Vue router layouts
Addition layout systems into an existing project on Vue using Vue router.
In this example we will consider a project on Vue2 options api with use Vue Router v3. In theory, I don’t see any particular problems in using it for Vue3 With composition api.
Repository with implementation
Task: speed up the creation of standard pages by means of layout systems with the ability to transfer the name of the required template for a page, approximately similar to the implementation in nuxt3.
Create a directory for our templates in the root of the project @/layouts
We design the main component in which we will define the required template:
// @/layouts/index.vue
<template>
<component :is="layoutComponent">
</template>
<script>
import "Наш компонент с лейаутом"
export default {
name: "layout",
components: { Наши компоненты layout },
computed: {
// генерируем имя нужного нам компонента заранее импортированного
// не делаю динамического импорта тк компонентов с лейаутами
// как правило не много, при необходимости можно добавить
layoutComponent() {
return `layout-${this.layoutType}`;
},
routeHaveLayoutType() {
return this.$route.meta && 'layout' in this.$route.meta;
},
// Определяем каой layout нам нужно использовать
// для указания используем $router.meta, по умолчанию default
// что будет равно нашему компоненту <layout-default />
layoutType() {
if (this.routeHaveLayoutType) {
return this.$route.meta.layout;
}
return "default";
},
}
}
</script>
Create a directory nearby for future templates @/layouts/components
We create default template for components:
// @/layouts/components/default.vue
<template>
// Ваша логика для default layout
// ключающая в себя router-view
// Например
<div class="container">
<header>My header</header>
<main>
<router-view />
</main>
<footer>My footer</footer>
</div>
</template>
Create an additional custom template tabswhich will implement the page in the form of tabs, where tabs are a link to the page:
// @/layouts/components/tabs
<template>
<div class="container">
<div class="tabs">
<RouterLink
v-for="(tab, index) in tabs"
:key="index"
:to="tab.path"
class="tabs__item"
active-class="is-active"
>
{{ tab.title }}
</RouterLink>
</div>
<div class="tabs__content">
<router-view />
</div>
</div>
</template>
<script>
export default {
name: "LayoutTabs",
props: {
tabs: {
type: Array,
default: () => [],
},
},
}
</script>
Create another additional template extra for demonstration:
// @/layouts/components/extra
<template>
<div class="container">
<heading>Extra layout</heading>
<router-view></router-view>
</div>
</template>
At the root of the project, in my case it is App.vue in the root of the project, we use the previously created component
<template>
<div id="app">
// Было
// <router-view />
// Стало
<layout />
</div>
</template>
Creating a basic structure routeraccording to the documentation vue-routerin the application or use the existing one:
const routes = [
{
path: "/",
component: () => import(Ваш компонент страницы),
...
}
...
]
To determine the required layout we use router.meta:
{
path: "/",
components: () => import(Ваш компонент),
meta: {
layout: "extra",
},
}
To define tabs, you can also use router.meta in parent routefor this we will need to update the code in ours a little @/layouts/index:
<template>
<component :is="layoutComponent" :tabs="layoutTabs" :parentRoute="parentRoute" />
</template>
<script>
import LayoutDefault from "./components/default.vue";
import LayoutExtra from "./components/extra.vue";
import LayoutTabs from "./components/tabs.vue";
export default {
name: "Layout",
components: {
LayoutDefault,
LayoutExtra,
LayoutTabs,
},
computed: {
routeHaveLayoutType() {
return this.$route.meta && 'layout' in this.$route.meta;
},
parentRouteHaveLayoutType() {
return this.parentRoute && this.parentRoute.meta && 'layout' in this.parentRoute.meta;
},
routeHaveLayoutTabs() {
return this.$route.meta && 'layoutTabs' in this.$route.meta;
},
parentRouteHaveLayoutTabs() {
return this.parentRoute && this.parentRoute.meta && 'layoutTabs' in this.parentRoute.meta;
},
layoutType() {
// проверяем содержит ли текущий route layout
if (this.routeHaveLayoutType) {
return this.$route.meta.layout;
}
// проверяем содержит ли родительский route layout
if (this.parentRouteHaveLayoutType) {
return this.parentRoute.meta.layout;
}
// возвращаем дефолтный по умолчанию если ничего не нашли
return "default";
},
layoutComponent() {
return `layout-${this.layoutType}`;
},
layoutTabs() {
if (this.layoutType === 'tabs') {
// если в текущем route указаны табы для layout то используем их
if (this.routeHaveLayoutTabs) {
return this.$route.meta.layoutTabs;
}
// пробуем найти перечисление табов в родительском route
if (this.parentRouteHaveLayoutTabs) {
return this.parentRoute.meta.layoutTabs;
}
return [];
}
return [];
},
// находим родительский route
parentRoute() {
const currentInMatched = this.$route.matched.find(i => i.regex.test(this.$route.path));
return currentInMatched && currentInMatched.parent;
}
},
}
</script>
If the project is completely on Vue And Vue-routeryou can get attached to children and from them form the structure of child tabs, for this we update computed in the component
{
computed: {
...
layoutTabs() {
// при использовании parentRoute.children как табы
if (this.parentChildren) {
return this.parentChildren.map((i, k) => {
return {
path: i.path,
title: 'tabTitle' in i.meta && i.meta.tabTitle || `tab${k}`,
}
});
}
// при передачи табов как meta.layoutTabs
if (this.layoutType === 'tabs') {
if (this.routeHaveLayoutTabs) {
return this.$route.meta.layoutTabs;
}
if (this.parentRouteHaveLayoutTabs) {
return this.parentRoute.meta.layoutTabs;
}
return [];
}
return [];
},
// находим children текущего route
parentChildren() {
if (!this.parentRoute) return [];
const parentRoute = this.$router.options.routes.find(i => this.parentRoute.regex.test(i.path) );
return parentRoute && parentRoute.children;
},
}
}
When using your own route in child routes layout and the presence of its own subsidiaries route we format the code in the following format:
[
{
path: "default",
component: () => import("компонент страницы"),
},
{
path: "tabs",
redirect: "/tabs/red",
meta: {
layout: "tabs",
layoutTabs: массив табов // если берем табы из meta
},
children: [
{
path: "red",
component: () => import("компонент заглушка который состоит из одного <router-view />"),
// кейс с еще более глубокой вложенностью и самостоятельным компонентом для родительского route "red"
children: [
{
// для корневого роута "red"
path: "",
component: () => import("ваш компонент red страницы"),
},
{
path: "orange",
component: () => import("ваш компонент oragne страницы"),
meta: {
layout: "extra",
},
},
{
path: "tomato",
component: () => import("ваш компонент tomato страницы"),
meta: {
layout: "extra",
},
}
],
},
{
path: "green"
component: () => import("ваш компонент green страницы"),
},
{
path: "blue",
component: () => import("ваш компонент blue страницы"),
}
]
}
]
The full implementation of the example can be seen at Github.