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");
}
}
};
</script></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.