Customização de Interface e Gerenciamento de Estado em Vue.js

Componente de Logomarca Lateral

Criaremos um módulo independente para gerenciar a identidade visual da barra lateral. Utilizaremos componentes nativos do Vue para transições suaves ao expandir ou recolher o menu. O arquivo será armazenado em src/layouts/Sidebar/Logo.vue.

<template>
  <div class="header-brand-wrapper" :class="{ 'collapsed': isReduced }">
    <transition name="fade-animation">
      <router-link
        v-if="isReduced"
        key="minimized"
        class="brand-link"
        to="/"
      >
        <img v-if="logoSrc" :src="logoSrc" class="brand-img" />
        <span v-else class="brand-text">{{ appTitle }}</span>
      </router-link>
      <router-link v-else key="expanded" class="brand-link" to="/">
        <img v-if="logoSrc" :src="logoSrc" class="brand-img" />
        <span class="brand-text">{{ appTitle }}</span>
      </router-link>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'AppHeaderLogo',
  props: {
    isReduced: {
      type: Boolean,
      required: true
    }
  },
  data() {
    return {
      appTitle: 'Painel Vue',
      logoSrc: 'https://cn.vuejs.org/images/logo.png'
    };
  }
};
</script>

No arquivo principle do sidebar (index.vue), devemos integrar este novo componente, garantindo que o método de verificação de visibilidade retorne o valor adequado inicialmente.

Barra Superior e Controle de Menu

A estrutura do cabeçalho (Navbar) compõe-se de múltiplos blocos funcionais, incluindo busca, seletor de idioma, configurações de tamanho e informações do usuário. Destacaremos a implementação do botão de hambúrguer para controle da sidebar.

Lógica de Estado com Vuex

A manipulação do estado do menu requer persistência entre sessões. Usaremos Cookies para salvar o status atualizado. Abaixo, uma revisão do módulo de aplicação no Vuex, renomeando as ações para clareza semântica:

import Cookies from "js-cookie";

const estadoInicial = {
  menu: {
    estendido: Cookies.get("menuDisplay")
      ? !!+Cookies.get("menuDisplay")
      : true,
    animacao: false
  },
  dispositivo: "desktop",
  localizacao: "pt",
  escala: Cookies.get("size") || "medium"
};

const mutacoes = {
  ALTERAR_MENU_VISUAL(state) {
    state.menu.estendido = !state.menu.estendido;
    state.menu.animacao = false;
    // Persistência no Cookie
    const status = state.menu.estendido ? 1 : 0;
    Cookies.set("menuDisplay", status);
  },
  ENCERRAR_MENU(state, semAnimacao) {
    Cookies.set("menuDisplay", 0);
    state.menu.estendido = false;
    state.menu.animacao = semAnimacao;
  },
  DEFINIR_TIPO_DISPOSITIVO(state, tipo) {
    state.dispositivo = tipo;
  },
  DEFINIR_IDIOMA(state, lang) {
    state.localizacao = lang;
    Cookies.set("language", lang);
  },
  DEFINIR_ESCALA(state, tamanho) {
    state.escala = tamanho;
    Cookies.set("size", tamanho);
  }
};

const acoes = {
  alternarMenu({ commit }) {
    commit("ALTERAR_MENU_VISUAL");
  },
  fecharMenu({ commit }, { semAnimacao }) {
    commit("ENCERRAR_MENU", semAnimacao);
  }
  // Outras ações omitidas...
};

export default {
  namespaced: true,
  estado: estadoInicial,
  mutacoes,
  acoes
};

Os getters devem ser expostos corretamente no arquivo de índice do store para permitir o acesso via mapa no componente da barra de navegação.

Botão de Hambúrguer

O componente Hamburger atua como o gatilho visual e funcional. Ele recebe o estado atual para aplicar classes CSS diferentes e dispara um evento personalizado ao ser clicado.

<template>
  <div style="padding: 0 15px;" @click="ativarAlternancia">
    <svg
      :class="{ 'menu-ativo': estadoEstendido }"
      class="icone-menu"
      viewBox="0 0 1024 1024"
      xmlns="http://www.w3.org/2000/svg"
      width="64"
      height="64"
    >
      <path
        d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
      />
    </svg>
  </div>
</template>

<script>
export default {
  name: 'MenuToggleBtn',
  props: {
    estadoEstendido: {
      type: Boolean,
      default: true
    }
  },
  methods: {
    ativarAlternancia() {
      this.$emit('trocarEstado');
    }
  }
};
</script>

<style scoped>
.icone-menu {
  display: inline-block;
  vertical-align: middle;
  width: 20px;
  height: 20px;
  transition: transform 0.3s;
}

.icone-menu.menu-ativo {
  transform: rotate(180deg);
}
</style>

A integração final ocorre no NavBar.vue. É crucial mapear os dados corretamente do Vuex e invocar a ação apropriada ao disparar o evento filho.

<template>
  <div class="painel-superior">
    <menu-toggle-btn
      id="controle-menu"
      :estado-estendido="menuInfo.estendido"
      class="botao-menu"
      @trocarEstado="dispararAlteracao"
    />
  </div>
</template>
<script>
import { mapGetters } from "vuex";
import MenuToggleBtn from "@/components/Hamburger";

export default {
  components: { MenuToggleBtn },
  computed: {
    ...mapGetters(["menuInfo", "avatarUsuario", "tipoDispositivo"])
  },
  methods: {
    dispararAlteracao() {
      this.$store.dispatch("app/trocarMenuVisivel");
    }
  }
};
&lt;/script&gt;</script>

Para assegurar consistência visual, especialmente nas variações de tamanho do item quando o menu está fechado, é necessário importar o arquivo de estilos globais no ponto de entrada da aplicação (main.js). Isso corrige divergências de alinhamento e espaçamento.

import "@/styles/index.scss";

Além da estilização, recomenda-se verificar ícones SVG personalizados para garantir que não haja conflitos de renderização quando houver links externos no menu.

Recursos Nativos do Vue.js

Durante o desenvolvimento deste layout, dois componentes fundamentais do núcleo foram empregados:

  • <transition>: Permite adicionar efeitos de entrada e saída para elementos. Ele não cria nós DOM adicionais no final, sendo transparente para a inspeção do componente pai.
  • <keep-alive>: Útil para preservar o estado de componentes dinâmicos. Ao envolver componentes que são criados e destruídos frequentemente, ele armazena a instância em cache em vez de destruí-la. Assim como o transition, é abstrato e não aparece na árvore de renderização real.

Tags: Vue.js Vuex ElementUI SCSS Single Page Application

Publicado em 8-13 20:31