Implementando Menu de Contexto Personalizado no el-tree do Element UI

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.

Tags: vuejs Element-UI javascript frontend

Publicado em 7-31 10:25