router, route 不知從何開始,寫後端都有一個 router 來控管程式進入的方式,透過 framework 框架的路徑模式,控管每個 function 的進入點來回傳內容,細部的切分讓程式的分工執掌更明確,統一的進入點讓程式的載入更有一致性。
以往麵條式的載入寫法都被視為非主流寫法,因為統一的調用更能夠掌握整個程式的運作模式,也衍伸了像是整個程式的生命週期問題,同步與非同步調用等設計模式。
開始寫 vue之後,沒想到前端也有個路由,加上之前看過的 class 繼承,越來越覺得 前端是不是跟後端也有點像了… 不過這也不是個壞東西,既然接了就要來弄懂他。
首先我看到的完整專案是 vue-element-admin ,是屬於 Element UI 的管理介面版本,因為整個系統都需要有權限控管,所以必須要了解 router 在 vue 裡面扮演什麼樣的角色。
首先根據介紹下指令裝好 Element Admin
# clone the project
git clone https://github.com/PanJiaChen/vue-element-admin.git
# enter the project directory
cd vue-element-admin
# install dependency
npm install
# develop
npm run dev
完成後會有許多目錄,可以看到 src/ 路徑下有個 router 的資料夾,在這邊放了 index.js 以及一個 modules,陣列裡面指定了 路由定義要載入哪一些 component。 不過這邊要提的重點應該是 index.js 內的 asyncRoutes
export const asyncRoutes = [
{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true, // will always show the root menu
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
roles: ['admin', 'editor'] // you can set roles in root nav
},
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
roles: ['admin'] // or you can only set roles in sub nav
}
},
{
path: 'directive',
component: () => import('@/views/permission/directive'),
name: 'DirectivePermission',
meta: {
title: 'Directive Permission'
// if do not set roles, means: this page does not require permission
}
},
{
path: 'role',
component: () => import('@/views/permission/role'),
name: 'RolePermission',
meta: {
title: 'Role Permission',
roles: ['admin']
}
}
]
},
vue-element-admin 的 路由邏輯,官方也做了文件解釋
仔細爬code可以在 /src/store/modules/permission.js 裡面看到 asyncRoutes 是 generateRoutes 模擬 Promise 接入使用者的角色開始重新建立 routes,不過主要的接入點還是 /src/store/modules/user.js 模擬登入後使用者帶入 Roles 角色之後,直接進行載入角色之後重新 filter 一次授權的路由,再重新塞回前端定義路由。
路由重新組成,這樣做的好處的確可以在傳遞上更為簡單,當你的使用者角色沒有這個路由,的確會沒有授權進入點的結果,但這個差異就差在每當後端想要定義角色可以進入的路由,前端就必須異動一次路由角色的授權,後端的確可以很單純的給予角色,讓前端控制衍伸出來的畫面就可以,不過這個方案不太適合應用到我的專案中。
*以上 vue-element-admin 的操作都為 vue2
以權限劃分路由
在我了解這個邏輯之後,認定一下 vue 的 router 主要都是屬於只要有寫上去,路徑對了就能過,想要達成權限控管的方式只有重新定義路由表,或是定義完畢後進入前,驗證路由權限
但要從後端重新定義路由表也會遇到兩個方式,一個根據後端給的結果,讓前端動態載入路由,第二個就是先確定好路由,再根據條件篩選路由。重新 reset 一次路由或是加入路由,也就是 vue-element-admin 的模式。
如果要使用定義的方式來寫路由,可以利用 router.beforeEach 這個方法來決定進入路由的權限,這個方式我讓前端的城市定義完整的路由,讓後端產生側邊欄的選單,也就是 DOM 產生,然後將後端每個項目的 key 值與前端路由的 name 做比對,如此一來就可以讓路徑與產生的畫面對上。
對上了之後再讓進入目標內容之前,取得使用者的權限,每次進入路由都驗證使用者的權限是否符合後端給予的權限,有的話就放行,沒有的話就跳無授權頁面。
為了自己好理解一下邏輯流程,利用 Draw io 畫了一下流程,不過為了分享把它放在筆記上了
https://hackmd.io/@mesak/r1oF43Plc

整理好路由之後就可以開始動工。
我的話是把登入資訊打成 vuex 的 auth module ,儲存 token 、 登入資訊、權限資訊、路由資訊,利用 action 把 往後端打 api 的模式透過非同步操作異動資料,再利用 getter 取得現有登入資訊
Router.beforeEach(async (to, _from, next) => {
// console.log(to, to.path, to.query)
if (store.getters['auth/hasToken']) {
if (store.getters['auth/isLogin']) {
if (to.path == '/home') {
next('/dashboard')
} else {
await store
.dispatch('auth/checkPermission', to.name)
.then(() => {
next()
})
.catch((error) => {
next('/permission-denied')
})
}
} else {
try {
await store.dispatch('auth/userInfo')
await Promise.all([store.dispatch('auth/getPermissions'), store.dispatch('auth/getRoutes')]).then(() => {
store
.dispatch('auth/checkPermission', to.name)
.then(() => {
next()
})
.catch((error) => {
next('/permission-denied')
})
})
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('auth/resetToken')
next(`/login?redirect=${to.path}`)
}
}
} else {
if (to.meta && to.meta.whitelist) {
next()
} else {
next({ path: '/login' })
}
}
})
有點小小的可惜是在驗是否登入這一塊,重複兩次了驗證權限的 code ,還沒有精簡他,因為要因應重整或是靜態 SPA 操作的模式,所以分成兩個方式重複操作同樣的事情,另外要做兩件事情是把 login 跟 home 加入白名單中 meta.whitelist 打成 true
避免因為無權限造成死迴圈,也要讓有登入的狀態下,避免回到無登入的頁面中,所以會驗證完有登入的操作,進入 home 都導向至 dashboard 中
下一篇將講述我的 router 如何驗證權限
以上操作為 vue3 實現