Vue router layouts

от автора

Добавление layout системы в существующий проект на Vue с использованием Vue router.

В данном примере будем рассматривать проект на Vue2 options api с использование Vue Router v3. В теории не вижу особых проблем в применении для Vue3 с composition api.

Репозиторий с реализацией

Задача: ускорить создание типовых страниц по средствам layout системы с возможностью передачи названия необходимого шаблона для страницы примерно похожей на реализацию в nuxt3.

Создаем директорию для наших шаблонов в корне проекта @/layouts

Оформляем основной компонент в котором будем определять нужный шаблон:

// @/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>

Создаем рядом директорию для будущих шаблонов @/layouts/components

Создаем default шаблон для компонентов:

// @/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>

Создаем дополнительно ещё один произвольный шаблон tabs, который будет реализовывать страницу в виде табов, где табы — ссылка на страницу:

// @/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>

Создаем еще один дополнительный шаблон extra для демонстрации:

// @/layouts/components/extra <template>   <div class="container">     <heading>Extra layout</heading>     <router-view></router-view>   </div> </template>

В корне проекта, в моем случае это App.vue в корне проекта, используем ранее созданный компонент <layout /> вместо существующего <router-view/>

<template>   <div id="app">     // Было   // <router-view />      // Стало     <layout />   </div> </template>

Создаем базовую структуру router, согласно документации vue-router, в приложении либо используем существующую:

const routes = [ { path: "/", component: () => import(Ваш компонент страницы), ... } ... ]

Для определения нужного layout используем router.meta:

{ path: "/", components: () => import(Ваш компонент), meta: { layout: "extra", }, }

Для определения табов так же можно используем router.meta в родительском route, для этого нужно будет немного обновить код в нашем @/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> 

Если проект полностью на Vue и Vue-router, можно привязаться к children и из них формировать структуру дочерних табов, для этого обновим computed в компоненте <layout/>:

{ 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;     }, } }

При использовании в дочерних route своего собственного layout и наличии его собственных дочерних route оформляем код в формате:

[ { 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 страницы"), } ] } ]

Полную реализацию примера можно посмотреть на Github


ссылка на оригинал статьи https://habr.com/ru/articles/829650/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *