差點就要開天窗了...,今天整理之前的介紹,實際用 Laravel 和 Nuxt 做了最雛形的網站包含了「註冊」、「登入」和「登出」功能,有興趣的鐵人大大可以到 GitLab 看完整的 code ! 今天主要是 Nuxt 部分的介紹。
一開始我們先準備好下列的 page components:
page component | 預期功能 |
---|
index.vue
| 除了做為系統官方首頁,同時對於登入者而言,也用來導向正確的角色 (管理者/一般用戶) 系統首頁register.vue
| 註冊一般用戶頁面,如果目前已經是登入狀態則導向首頁login.vue
| 登入頁面,如果目前已經是登入狀態則導向首頁
在 register.vue
和 login.vue
當中,除了昨天介紹過的表單寫法之外,主要的重點在 asyncData
的部份,當頁面載入的時候要先檢查是否已經存有 token (即登入的狀態),若已經登入就要跳轉至首頁:
async asyncData({ store, redirect }) {
const hasToken = store.getters['hasToken'];
if (hasToken) {
redirect('/');
}
// register 和 login 註冊的表單
},
在 login.vue
的 asyncData
裡面,用來判斷受否已經登入以及登入者的角色並用來跳轉頁面:
export default {
name: 'index',
async asyncData({ app, store, redirect }) {
const hasToken = store.getters.userToken;
if (hasToken) {
const isManager = store.getters['user/isManager'];
const isUser = store.getters['user/isUser'];
if (isManager) {
redirect('/manager');
}
if (isUser) {
redirect('/user');
}
}
return {};
}
}
剛剛上面有用到 Vuex store module 的資料,所以建立下面的 store module:
module | 資料類型 |
---|
index.js
| 存放需要記錄在 cookie 的資料user.js
| 存放登入者的使用資訊
為了方便控管,同時也是藉由 nuxtServerInit
方法儲存 cookie 相關資料。在今天的例子只有 userToken
一個資料:
const state = () => {
return {
userToken: undefined,
};
};
const actions = {
nuxtServerInit({ commit }, { req }) {
const hasCookieInReq = !!req.headers.cookie;
if (hasCookieInReq) {
try {
const allCookies = cookieparser.parse(req.headers.cookie);
const token = allCookies['auth-token'];
commit('SET_USER_TOKEN', token);
} catch (error) {
// ...
}
}
},
// ...
在 user.js
的 getters 中,除了取得儲存的 user 同時也特別增加了 hasUser
,這是用來確認目前的 store 裡面有沒有資料,假若頁面可以取得 token 但卻沒有 user 資料,代表 Vuex 的資料揮發了,需要透過 API 重新取得資料。
const state = () => {
return {
user: {
name: undefined,
email: undefined,
role: undefined,
},
};
};
// ...
const getters = {
hasUser(state) {
return !!state.user.role;
},
// ...
};
由於任一個 page component 都可能受到 Vuex 資料揮發的影響,因此我們在 middleware
底下加入 auth.js
,並且將它掛在 nuxt.config.js
的 router
屬性下,這樣載入任何一頁都會先檢查是否存在 「有 userToken 但是沒有 user」 的情況:
import { validators } from '@/configs/api/user';
export default async ({ app, store }) => {
const hasToken = store.getters.hasToken;
const hasUser = store.getters['user/hasUser'];
if (hasToken) {
if (!hasUser) {
try {
const { data } = await app.$api(validators.AccessUserRequest, { userId: null });
await store.dispatch('user/setUser', data);
} catch (error) {
await store.dispatch('removeUserToken');
}
}
}
};
順帶一提,因為 JWT 是有時效性的,所以假若 token 已經過期,無法取得 user 資料時,我們會將 userToken 清除,讓使用者可以再次登入。
至目前為止「註冊」和「登入」功能完成了,但「路由依角色跳轉」功能只完成一半 (首頁的部份),我們很常會直接打上特定網址瀏覽頁面,所以另一半是要撰寫在 pages/manager
底下的頁只有管理者可以瀏覽,pages/user
則相反,所以我們增加下列資檔案:
neast page component | 功能 |
---|
manager.vue
| 將所有 pages/manager
底下的 page components 共同需要的設定即畫面設定於此user.vue
| 將所有 pages/user
底下的 page components 共同需要的設定即畫面設定於此
目前上述兩者只需要設定 middleware 即可。
middleware | 功能 |
---|
managerCheck.js
| 檢查 Vuex store user
module 裡面儲存的角色是否為管理者
userCheck.js
| 檢查 Vuex store user
module 裡面儲存的角色是否為一般使用者
在上述兩個 middlewares 中,我們都只檢驗個別角色而已,同時只要有不符合的情況一律轉到 pages/index.vue
,這樣假設未來系統角色變多了,實際判斷要跳轉的角色首頁都在 pages/index.vue
做就可以了。
export default async ({ store, redirect }) => {
const hasToken = store.getters.hasToken;
const isUser = store.getters['user/isUser'];
if (!(hasToken && isUser)) {
redirect('/');
}
};
最後我們來補上「登出」功能就可以收工啦! 因為不論是管理者或是一般使用者都要有登出功能,在 pages/manager.vue
和 pages/user.vue
各寫一次也可以,但同樣的假設未來角色變多就很麻煩,所以我們可以把「登出」寫在 layouts
,這樣在 neast page components 要修改的幅度就少很多了!
下面是 layout/baseLayout.vue
<template>
<Row class="base-layout">
<Row class="head">
<!-- ... -->
<button class="logout-btn" @click="logoutAction">登出</button>
</Row>
<Row class="body">
<nuxt />
</Row>
</Row>
</template>
<script>
import { mapActions } from 'vuex';
import { validators } from '@/configs/api/user';
export default {
name: 'BaseLayout',
data() {
return {
storeUser: undefined,
};
},
methods: {
...mapActions(['removeUserToken']),
async logoutAction() {
try {
const { status, code } = await this.$api(validators.LogoutRequest, {});
const successLogout = status && code === 200;
if (successLogout) {
this.removeUserToken();
this.redirectToIndex();
}
} catch (error) {
}
},
redirectToIndex() {
this.$router.push({ name: 'index' });
}
},
async asyncData({ store }) {
const storeUser = store.getters['user/storeUser'];
return {
storeUser,
}
}
}
</script>
pages/manager.vue
和 pages/user.vue
各加入 layouts
的設定:
// ...
layout: 'baseLayout',
}
以上就是「註冊」、「登入」和「登出」搭配多角色的權限控管以跳轉範例,大家可以玩玩看。 今天的範例畫面都醜到炸,明天介紹 SCSS 的基本應用跟小弟個人的小經驗,至少有一些基礎之後不會被說工程師都不懂美感 XD