Vue: як зробити кілька шаблонів в SPA

Vue: як зробити кілька шаблонів в SPA

Нерідкі ситуації, коли для різних сторінок сайту використовуються різні шаблони. Найпростіший приклад: один шаблон для сторінки авторизації і інший - для всього іншого. Розглянемо пару способів реалізації такого функціоналу в односторінкових додатках Vue.

Будемо виходити з того, що в нас вже є створений Vue-додаток за допомогою vue-cli (який містить vuex та vue-router). Якщо Ви не знаєте як це зробити, рекомендую ознайомитися з цією документацією, або передивитися це коротко відео. Для стилізації використаємо materialize-css. Тобто необхідно виконати команду:

npm install materialize-css

 

Спосіб 1 - vuex та метадані маршрутів

Насамперед в каталозі src створюємо підкаталог layouts, після чого структура проекту матиме приблизно такий вигляд:

Наступний крок - увійдемо в підкаталог і реалізуємо шаблон за замовчуванням і шаблон для входу в додаток.

Код AuthLayout.vue:

<template>
  <div class="auth-wrapper">
    <slot/>
  </div>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped>
.auth-wrapper {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: linear-gradient(to bottom right, #004d40, #00bfa5);
}
</style>

 

та DefaultLayout.vue:

<template>
  <div>
    <nav class="teal accent-4">
      <div class="nav-wrapper container">
        <router-link class="brand-logo" to="/">MultiLayouts</router-link>|
        <ul id="nav-mobile" class="right hide-on-med-and-down">
          <li>
            <router-link to="/">Home</router-link>|
          </li>
          <li>
            <router-link class="waves-effect grey darken-1 btn" to="/login">Sign in</router-link>
          </li>
        </ul>
      </div>
    </nav>
    <div class="container">
      <slot/>
    </div>
  </div>
</template>

<script>
export default {
  name: 'DefaultLayout'
}
</script>

<style lang="scss">
#nav {
  padding: 30px;
  a {
    font-weight: bold;
    color: #2c3e50;
    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

 

Змінимо код App.vue, додавши динамічне перемикання компонентів. При цьому зробимо так - будемо використовувати шаблон, який вказаний у властивості meta.layout, якщо ж така властивість не вказана - нам потрібен шаблон за замовчуванням:

<template>
  <div id="app">
    <component :is="layout">
      <router-view/>
    </component>
  </div>
</template>

<script>
export default {
  computed: {
    layout() {
      return this.$route.meta.layout || 'default-layout'
    }
  }
}
</script>

 

Також в каталозі views додамо пару найпростіших компонентів. Home.vue:

<template>
  <div class="home">
    <h1>Home page</h1>
  </div>
</template>

<script>
export default {
  name: 'home',
  components: {
    HelloWorld
  }
}
</script>

 

і Login.vue з більш-менш простою формою:

<template>
  <div class="card auth-form">
    <div class="card-content">
      <div class="card-title">Sign in</div>
      <form action="#">
        <div class="input-field">
          <i class="material-icons prefix">email</i>
          <input id="email" type="email" class="validate">
          <label for="email">Email</label>
        </div>

        <div class="input-field">
          <i class="material-icons prefix">vpn_key</i>
          <input id="password" type="password" class="validate">
          <label for="password">Password</label>
        </div>

        <div class="btn-group">
          <router-link class="btn waves-effect waves-light grey" to="/">
            Back
            <i class="material-icons left">keyboard_backspace</i>
          </router-link>
          <button class="btn waves-effect waves-light" type="submit">
            Submit
            <i class="material-icons right">send</i>
          </button>
        </div>
      </form>
    </div>
  </div>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped>
.auth-form {
  min-width: 400px;

  .input-field {
    margin-bottom: 2rem;
  }

  .card-title {
    margin-bottom: 2rem;
    text-align: center;
  }

  .btn-group {
    display: flex;
    justify-content: space-between;
  }
}
</style>

 

Основа є. Тепер треба змусити це все працювати. Зберігати поточне значення шаблону будемо у Vuex. Код файлу store.js:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    layout: 'default-layout'
  },
  mutations: {
    setLayout(state, payload) {
      state.layout = payload
    }
  },
  actions: {},
  getters: {
    layout(state) {
      return state.layout
    }
  }
})

 

Пропишемо маршрути (router.js):

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('@/views/Home.vue')
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('@/views/auth/Login.vue'),
      meta: {
        layout: 'auth-layout'
      }
    }
  ]
})

 

Та збираємо все в main.js:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import 'materialize-css/dist/css/materialize.css'

import DefaultLayout from './layouts/DefaultLayout.vue'
import AuthLayout from './layouts/AuthLayout.vue'

Vue.component('default-layout', DefaultLayout)
Vue.component('auth-layout', AuthLayout)

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

 

Готово!

 

Спосіб 2 - пакет vue-extend-layout

Треба зауважити, що струтура і файли будуть практично тими ж, що і раніше, за винятком того, що vuex для перемикання шаблонів не знадобиться. До пакету додається непогана документація, і, відверто кажучи, наступне буде лише її незначною варіацією. Отже, встановлюємо розширення:

npm install vue-extend-layout --save

 

Зосередимося на відмінностях. App.vue тепер виглядатиме ось так:

<template>
  <div id="app">
    <vue-extend-layouts/>
  </div>
</template>

<script>
import VueExtendLayouts from 'vue-extend-layout'

export default {
  name: 'App',
  components: { VueExtendLayouts }
}
</script>

 

І в шаблонах більше немає слотів (приведу код auth.vue):

<template>
  <div class="auth-wrapper">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'auth'
}
</script>

<style lang="scss" scoped>
.auth-wrapper {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: linear-gradient(to bottom right, #004d40, #00bfa5);
}
</style>

 

Як вже говорилося, обійдемося без сховища. Що стосується роутів - властивість мета вказуємо для кожного з них (router.js):

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('./views/Home'),
      meta: {
        layout: 'default'
      }
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('./views/Login.vue'),
      meta: {
        layout: 'auth'
      }
    }
  ]
})

 

Сторінки-компоненти Home.vue і Login.vue нічим не відрізняються від описаних в попередньому варіанті, тому дублювати код не має сенсу. І наостанок main.js:

import Vue from 'vue'
import App from './App.vue'
import router from './router'

import 'materialize-css/dist/css/materialize.css'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

 

Ось, власне, і все. Успіхів!