VaAppBar:sidebar

Vuetify is the default customizable section with hierarchical menu with VNavigationDrawer component. Sidebar menu is located within the VaAppBar component.

packages/admin/src/components/layout/AppBar.vue

SideBar

src/layout/Admin.vue

<template>
  <v-app>
    <va-layout v-if="authenticatedUser">
      <template>
        <va-app-bar :key="getNavbarKey" color="primary" density="compact" elevation="1" :header-menu="getHeaderMenu" sidebar-color="white">
          <template v-slot:navbar-logo>
            <div class="text-center mt-12 mb-5 mr-6 text-primary" style="font-size: 26px;">
              Logo
            </div>
          </template>

          <template v-slot:profile>
            <v-menu offset-y>
              <template v-slot:activator="{ props }">
                <v-btn icon small v-bind="props" class="mr-1">
                 <div v-if="avatarExists" style="float:left;">
                    <v-avatar size="24px">
                      <v-img :src="getAvatar" alt="Avatar"></v-img>
                    </v-avatar>
                  </div>
                  <div v-else style="float:left;">
                    <v-icon>mdi-account-circle</v-icon>
                  </div>
                </v-btn>
              </template>
              <v-card min-width="300">
                <v-list nav>
                  <v-list-item class="mb-2 mt-2" v-if="getFullname" :prepend-avatar="getAvatar">
                    <div class="list-item-content">
                      <v-list-item-title class="title">{{ getFullname }}</v-list-item-title>
                      <v-list-item-subtitle v-if="getEmail">{{ getEmail }}</v-list-item-subtitle>
                    </div>
                  </v-list-item>
                  <v-divider></v-divider>
                  <v-card flat class="mt-2">
                    <v-card-text style="padding:0px;">
                      <v-list-item v-for="(item, index) in getProfileMenu" :key="index" link :to="item.link" logout : null>
                        <template v-slot:prepend>
                          <v-icon>{{ item.icon }}</v-icon>
                        </template>
                        <v-list-item-title>{{ item.text }}</v-list-item-title>
                      </v-list-item>
                    </v-card-text>
                  </v-card>
                </v-list>
              </v-card>
            </v-menu>
          </template>

        </va-app-bar>
      </template>

      <template>
        <va-breadcrumbs></va-breadcrumbs>
      </template>

      <template>
        <va-aside></va-aside>
      </template>

      <template>
        <va-footer :key="getCurrentLocale" :menu="getFooterMenu">
          <template v-slot:left>
            <languageswitcher></languageswitcher>
          </template>
          <template v-slot:right>
            <span style="font-size:13px">© 2024</span>
          </template>
        </va-footer>
      </template>
    </va-layout>
  </v-app>
</template>
<script>
import isEmpty from "lodash/isEmpty"
import { useDisplay } from "vuetify";
import Trans from "@/i18n/translation";
import LanguageSwitcher from "@/components/LanguageSwitcher.vue";
import { storeToRefs } from 'pinia'
import useAuth from "olobase-admin/src/store/auth";

export default {
  name: "App",
  inject: [],
  components: { LanguageSwitcher },
  setup() {
    const { lgAndUp } = useDisplay();
    return { lgAndUp };
  },
  data() {
    return {
      avatar: null,
      avatarExists: false,
      email: null,
      fullname: null,
      authenticatedUser: null,
    };
  },
  async created() {
    /**
     * Set default locale
     */
    const lang = Trans.guessDefaultLocale();
    if (lang && Trans.supportedLocales.includes(lang)) { // assign browser language
      await Trans.switchLanguage(lang);
    }
    /**
     * Check user is authenticated
     */
    this.authenticatedUser = await this.$store.getModule("auth").checkAuth();
    if (! this.authenticatedUser) {
      this.$router.push({name: "login"});
    } else {
      this.email = this.$store.getModule("auth").getEmail;
      this.fullname = this.$store.getModule("auth").getFullname;
      // 
      // Please do not remove it, it periodically updates the session lifetime.
      // Thus, as long as the user's browser is open, the user logged in to the application,
      // otherwise the session will be closed when the ttl period expires.
      // 
      this.$admin.http.post('/auth/session', { update: 1 }); 
    }
    /**
     * Check avatar
     */
    let base64Image = this.$store.getModule("auth").getAvatar;
    if (base64Image == "undefined" || base64Image == "null" || isEmpty(base64Image)) { 
      this.avatar = this.$admin.getConfig().avatar.base64; // default avatar image
      this.avatarExists = false;
    } else {
      this.avatarExists = true;
      this.avatar = base64Image;
    }
  },
  computed: {
    getNavbarKey() {
      return this.$store.navbarKey + '_' + this.$store.getLocale;
    },
    getCurrentLocale() {
      const { locale } = storeToRefs(this.$store);
      return locale;
    },
    getAvatar() {
      return this.avatar;
    },
    getEmail() {
      return this.email;
    },
    getFullname() {
      return this.fullname;
    },
    getHeaderMenu() {
      return [];
    },
    getProfileMenu() {
      return [
        {
          icon: "mdi-account",
          text:  this.$t("va.account"),
          link: "/account",
        },
        {
          icon: "mdi-key-variant",
          text: this.$t("va.changePassword"),
          link: "/password",
        },
        {
          icon: "mdi-logout",
          text: this.$t("va.logout"),
          logout: true,
        },
      ];
    },
    getFooterMenu() {
      return []
    }
  },
  methods: {
    logout() {
      this.$store.getModule("auth").logout();
      this.$router.push({ name: "login" });
    },
  },
};
</script>

Sidebar Menu Links

Any navigation menu that can be placed in components that support navigation is a simple array of objects that represent a link. Components that support navigation; It consists of a total of 4 different components: header, profile, sidebar and footer menu.

Menus are created by sending a simple menu array as follows to these components.

[
  {
    icon: "mdi-account",
    text: this.$t("menu.profile"),
    link: "/profile",
  },
  {
    icon: "mdi-settings",
    text: this.$t("menu.settings"),
    link: "/settings",
  },
]

Sidebar menu example:

src/_nav.js

export default  {

  build: async function(t, admin) {

    return [
      {
        icon: "mdi-view-dashboard-outline",
        text: t("menu.dashboard"),
        link: "/dashboard",
      },
      {
        icon: "mdi-cart-variant",
        text: t("menu.plans"),
        children: [
          {
            icon: "circle",
            text: t("menu.subscriptions"),
            link: "/subscriptions",
          },
          {
            icon: "circle",
            text: t("menu.services"),
            link: "/services",
          },
        ]
      },
    ];

  } // end func

} // end class

To create the menu dynamically based on authorizations, see related document.

Object Link Structure

Property Type Description
icon string The menu icon shown on the left. Must be a valid mdi and is only supported in sidebar and profile menu.
text string Link tag.
link string | object Current Vue router menu. It cannot be used with child connections.
href string Use for external links only. The target value is sent as empty. It cannot be used with child connections.
children array Used only for sublinks to the hierarchical menu for the sidebar. Cannot be used with link or href.
expanded boolean Shows Children links as expanded by default. It only affects links that are children.
heading string It just turns the link into a section label title for the sidebar menu. All other features within it are disabled.
divider boolean It turns the link into a separator for the sidebar menu only. All other features within it are disabled.