Construindo um Editor de Fluxograma com Suporte a Arrastar e Soltar Usando Vue3 e AntV X6
O desenvolvimento de ferramentas visuais para edição tornou-se essencial no cenário atual da programação front-end. Seja para criar designer de fluxos de trabalho, representar arquiteturas de sistemas ou visualizar relações entre dados, precisamos de soluções图形高效。O AntV X6 se destaca como um mecanismo gráfico robusto, e quando combinado com o Vue3, oferece uma experiência de desenvolvimento excepcional. Neste guia, você aprenderá a implementar rapidamente um editor de fluxograma com funcionalidade de arrastar e soltar, além de explorar técnicas avançadas para criar nodes personalizados.
Preparação do Ambiente e Integração Básica
Inicie criando um projeto Vue3 utilizando o Vite:
npm create vite@latest meu-projeto-x6 --template vue
cd meu-projeto-x6
Instale a biblioteca principal do AntV X6:
npm install @antv/x6 --save
Para projetos Vue3, é necessário instalar também o pacote de suporte a nodes Vue:
npm install @antv/x6-vue-shape --save
A seguir, apresentamos o código básico para integração:
<template>
<div id="canvas" class="x6-graph"></div>
</template>
<script setup>
import { onMounted } from 'vue'
import { Graph } from '@antv/x6'
import { register } from '@antv/x6-vue-shape'
onMounted(() => {
const diagrama = new Graph({
container: document.getElementById('canvas'),
width: 800,
height: 600,
grid: true,
background: {
color: '#f5f5f5'
}
})
})
</script>
<style>
.x6-graph {
border: 1px solid #e2e2e3;
border-radius: 4px;
}
</style>
Implementando a Funcionalidade de Arrastar e Soltar
Para permitir que o usuário arraste elementos do painel lateral para a área de desenho, precismaos tratar três eventos importantes:
- Início do arraste: Armazenar os dados do elemento que está sendo movido
- Mover sobre o canvas: Permitir o comportamento de soltar através de event.preventDefault()
- Soltar o elemento: Criar o nó no local onde o usuário soltou
<template>
<div class="editor-layout">
<div class="sidebar">
<div
v-for="item in tiposNodos"
:key="item.tipo"
class="draggable-item"
draggable="true"
@dragstart="iniciarArraste(item)"
>
{{ item.rotulo }}
</div>
</div>
<div
id="canvas"
class="x6-graph"
@dragover="permitirSoltura"
@drop="processarSolta"
></div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Graph } from '@antv/x6'
const tiposNodos = ref([
{ tipo: 'inicio', rotulo: 'Nó Inicial' },
{ tipo: 'termino', rotulo: 'Nó Final' },
{ tipo: 'processamento', rotulo: 'Processamento' },
{ tipo: 'decisao', rotulo: 'Decisão' }
])
const nodoArrastado = ref(null)
const diagrama = ref(null)
onMounted(() => {
diagrama.value = new Graph({
container: document.getElementById('canvas'),
width: 800,
height: 600,
grid: true,
connecting: {
router: 'manhattan',
connector: 'rounded'
}
})
})
const iniciarArraste = (item) => {
nodoArrastado.value = item
}
const permitirSoltura = (evento) => {
evento.preventDefault()
}
const processarSolta = (evento) => {
evento.preventDefault()
if (!nodoArrastado.value || !diagrama.value) return
const coordenadas = diagrama.value.clientToLocal(evento.clientX, evento.clientY)
adicionarNodo(nodoArrastado.value.tipo, coordenadas.x, coordenadas.y)
}
const adicionarNodo = (tipo, x, y) => {
const configBase = {
x,
y,
width: 120,
height: 40,
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 4,
ry: 4
},
label: {
text: nodoArrastado.value.rotulo,
fill: '#333',
fontSize: 12
}
}
}
switch (tipo) {
case 'inicio':
diagrama.value.addNode({
...configBase,
shape: 'rect',
attrs: {
...configBase.attrs,
body: {
...configBase.attrs.body,
fill: '#ffd591'
}
}
})
break
case 'termino':
diagrama.value.addNode({
...configBase,
shape: 'ellipse',
attrs: {
...configBase.attrs,
body: {
...configBase.attrs.body,
fill: '#ffccc7'
}
}
})
break
default:
diagrama.value.addNode(configBase)
}
}
</script>
<style>
.editor-layout {
display: flex;
height: 100vh;
}
.sidebar {
width: 200px;
padding: 10px;
border-right: 1px solid #e8e8e8;
}
.draggable-item {
padding: 8px 12px;
margin-bottom: 8px;
background: #fafafa;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: move;
}
.draggable-item:hover {
border-color: #1890ff;
color: #1890ff;
}
.x6-graph {
flex: 1;
border: 1px solid #e2e2e3;
}
</style>
Criação de Nodes Personalizados Avançados
Uma das funcionalidades mais poderosas do AntV X6 é a capacidade de utilizar componentes Vue como conteúdo de nodes. A seguir, implementaremos um nó personalizado que exibe uma barra de progresso:
Primeiro, crie o componente do nó personalizado:
<!-- components/NodoProgresso.vue -->
<template>
<div class="custom-progress-node">
<div class="header">{{ dadosNodo.titulo }}</div>
<div class="progress-container">
<div
class="progress-bar"
:style="{ width: `${progresso}%` }"
></div>
<span class="progress-text">{{ progresso }}%</span>
</div>
</div>
</template>
<script setup>
import { ref, defineProps } from 'vue'
const props = defineProps({
dadosNodo: {
type: Object,
required: true
}
})
const progresso = ref(props.dadosNodo.valor || 0)
</script>
<style scoped>
.custom-progress-node {
width: 100%;
height: 100%;
padding: 8px;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.header {
font-weight: bold;
margin-bottom: 6px;
}
.progress-container {
position: relative;
height: 20px;
background: #f0f0f0;
border-radius: 3px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: #1890ff;
transition: width 0.3s ease;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 11px;
color: #333;
}
</style>
Agora, registe o componente e utilize-o no fluxograma:
<script setup>
import { onMounted, ref } from 'vue'
import { Graph } from '@antv/x6'
import { register } from '@antv/x6-vue-shape'
import NodoProgresso from './components/NodoProgresso.vue'
// Registrando o componente Vue como um nó do X6
register({
shape: 'vue-progress-node',
width: 180,
height: 70,
component: NodoProgresso
})
const diagrama = ref(null)
const dadosNodoProgresso = ref({
titulo: 'Projeto Alpha',
valor: 65
})
onMounted(() => {
diagrama.value = new Graph({
container: document.getElementById('canvas'),
width: 800,
height: 600,
grid: true
})
// Adicionando o nó personalizado ao canvas
diagrama.value.addNode({
shape: 'vue-progress-node',
x: 300,
y: 200,
dadosNodo: dadosNodoProgresso.value
})
})
</script>
Considerações Finais
O AntV X6 integrada ao Vue3 proporciona uma base sólida para construção de editores visuais complexos. As técnicas apresentadas neste artigo — desde a configuração inicial até a criação de componentes Vue como nodes — permitem desenvolver soluções personalizadas para diversas necessidades de edição graphical.
Para expandir ainda mais este projeto, considere implementar funcionalidades como salvamento e carregamento de diagramas, conexão entre nodes com validação, toolbar de ferramentas e recursos de zoom e pan.