
Vue: як зробити кілька шаблонів в SPA
31.12.2018 17:09 | Vue
Нерідкі ситуації, коли для різних сторінок сайту використовуються різні шаблони. Найпростіший приклад: один шаблон для сторінки авторизації і інший - для всього іншого. Розглянемо пару способів реалізації такого функціоналу в односторінкових додатках 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')
Ось, власне, і все. Успіхів!