Estrutura Básica do Componente de Árvore
Para adicionar a funcionalidade de clique com o botão direito em um componente el-tree do Vue 2, utilizaremos o evento nativo node-contextmenu. Primeiro, definimos a estrutura básica do componente no template.
<template>
<div>
<el-tree
ref="treeRef"
:data="hierarquiaDados"
node-key="id"
highlight-current
:props="defaultProps"
@node-contextmenu="manipularMenuContexto"
@node-expand="ocultarMenu"
@node-collapse="ocultarMenu"
>
</el-tree>
<!-- Menu flutuante customizado -->
<div v-show="menuVisivel" class="context-menu-container" :style="posicaoMenu">
<div @click="acaoAdicionar">
<i class="el-icon-circle-plus-outline" style="color: #409EFF"></i>
Novo Subitem
</div>
<div @click="acaoEditar">
<i class="el-icon-edit" style="color: #E6A23C"></i>
Renomear
</div>
<div @click="acaoExcluir">
<i class="el-icon-delete" style="color: #F56C6C"></i>
Remover Item
</div>
</div>
</div>
</template>
Configuração de Dados e Estilização
O menu de contexto deve ser posicionado de forma absoluta ou fixa para que acompanhe o cursor do mouse. Utilizamos o z-index elevado para garantir que ele sobrepnoha outros elementos da interface.
<style lang="less" scoped>
.context-menu-container {
position: fixed;
background: #ffffff;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
z-index: 3000;
padding: 5px 0;
min-width: 150px;
div {
padding: 8px 15px;
font-size: 13px;
color: #606266;
cursor: pointer;
transition: background 0.2s;
&:hover {
background-color: #f5f7fa;
color: #409EFF;
}
i {
margin-right: 8px;
}
}
}
</style>
Lógica de Posicionamento e Eventos
No script, capturamos as coordenadas clientX e clientY do evento de clique. Além disso, registramos um ouvinte global no document para fechar o menu quando o usuário clicar em qualquer outra área da aplicação.
<script>
export default {
data() {
return {
menuVisivel: false,
noSelecionado: null,
posicaoMenu: {
top: '0px',
left: '0px'
},
defaultProps: {
children: 'subfolders',
label: 'titulo'
},
hierarquiaDados: [
{
id: 101,
titulo: 'Matriz São Paulo',
subfolders: [
{ id: 201, titulo: 'Departamento TI', subfolders: [] },
{ id: 202, titulo: 'Recursos Humanos', subfolders: [] }
]
},
{
id: 102,
titulo: 'Filial Curitiba',
subfolders: [
{ id: 301, titulo: 'Logística', subfolders: [] }
]
}
]
};
},
methods: {
manipularMenuContexto(event, data) {
// Impede o menu padrão do navegador
event.preventDefault();
this.noSelecionado = data;
this.menuVisivel = true;
// Define a posição baseada no clique
this.posicaoMenu.top = `${event.clientY}px`;
this.posicaoMenu.left = `${event.clientX}px`;
// Adiciona eventos para fechar o menu ao clicar fora
document.addEventListener('click', this.ocultarMenu);
document.addEventListener('contextmenu', this.ocultarMenu);
},
ocultarMenu() {
this.menuVisivel = false;
document.removeEventListener('click', this.ocultarMenu);
document.removeEventListener('contextmenu', this.ocultarMenu);
},
acaoAdicionar() {
console.log('Criando subitem em:', this.noSelecionado.titulo);
},
acaoEditar() {
console.log('Editando item:', this.noSelecionado.id);
},
acaoExcluir() {
this.$confirm(`Deseja remover "${this.noSelecionado.titulo}"?`, 'Confirmação', {
type: 'warning'
}).then(() => {
console.log('Item removido com sucesso');
}).catch(() => {});
}
}
};
</script>
Considerações de Implementação
Ao lidar com menus de contexto em componentes de árvore, é importante observar que eventos de expansão ou recolhimento de nós (@node-expand e @node-collapse) também devem disparar o fechamento do menu para evitar que ele fique "pendurado" em uma posição inconsistente após o redesenho da árvore. O uso de position: fixed garante que o menu seja posicionado em relação à janela de visualização (viewport), evitando problemas de overflow caso o el-tree esteja dentro de um container com scroll.