基于vue,vue-router, vuex及addRoutes進(jìn)行權(quán)限控制問(wèn)題

基于vuex, vue-router,vuex的權(quán)限控制教程,完整代碼地址見(jiàn) https://github.com/linrunzheng/vue-permission-control
接下來(lái)讓我們模擬一個(gè)普通用戶打開(kāi)網(wǎng)站的過(guò)程,一步一步的走完整個(gè)流程。
首先從打開(kāi)本地的服務(wù)localhost:8080開(kāi)始,我們知道打開(kāi)后會(huì)進(jìn)入login頁(yè)面,那么判斷的依據(jù)是什么。
首先是token。
沒(méi)有登陸的用戶是獲取不到token的,而登陸后的角色我們會(huì)將token存到local或者seesionStorage 因此,根據(jù)當(dāng)前有沒(méi)有token即可知道是否登陸。
為了存取token并且方便我們操作,可以配和vuex實(shí)現(xiàn)
/* state.js */
export default {
get UserToken() {
return localStorage.getItem('token')
},
set UserToken(value) {
localStorage.setItem('token', value)
}
}
/* mutation.js */
export default {
LOGIN_IN(state, token) {
state.UserToken = token
},
LOGIN_OUT(state) {
state.UserToken = ''
}
}
攔截的判斷
沒(méi)有token進(jìn)入需要權(quán)限的頁(yè)面:redirect到login頁(yè)面
由于我們路由是動(dòng)態(tài)掛載的,包括 ' ' 和404,所以當(dāng)匹配不到路由時(shí),也重定向到login
router.beforeEach((to, from, next) => {
if (!store.state.UserToken) {
if (
to.matched.length > 0 &&
!to.matched.some(record => record.meta.requiresAuth)
) {
next()
} else {
next({ path: '/login' })
}
}
})
好了,此時(shí)用戶打開(kāi)localhost:8080,默認(rèn)匹配的是''路徑,此時(shí)我們并沒(méi)有掛載路由,也沒(méi)有token,所以來(lái)到了login。
輸入用戶名密碼后,有token了,通過(guò)store觸發(fā)* commit('LOGIN_IN')* 來(lái)設(shè)置token。
但是還是沒(méi)有路由,目前最開(kāi)始只有l(wèi)ogin路由
/* 初始路由 */
export default new Router({
routes: [
{
path: '/login',
component: Login
}
]
})
/* 準(zhǔn)備動(dòng)態(tài)添加的路由 */
export const DynamicRoutes = [
{
path: '',
component: Layout,
name: 'container',
redirect: 'home',
meta: {
requiresAuth: true,
name: '首頁(yè)'
},
children: [
{
path: 'home',
component: Home,
name: 'home',
meta: {
name: '首頁(yè)'
}
}
]
},
{
path: '/403',
component: Forbidden
},
{
path: '*',
component: NotFound
}
]
我們要根據(jù)當(dāng)前用戶的token去后臺(tái)獲取權(quán)限。
由于權(quán)限這塊邏輯還挺多,所以在vuex添加了一個(gè)permission模塊來(lái)處理權(quán)限。
為了判斷是已有路由列表,需要在vuex的permission模塊存一個(gè)state狀態(tài)permissionList用來(lái)判斷,假如permissionList不為null,即已經(jīng)有路由,如果不存在,就需要我們干活了。
router.beforeEach((to, from, next) => {
if (!store.state.UserToken) {
...
} else {
/* 現(xiàn)在有token了 */
if (!store.state.permission.permissionList) {
/* 如果沒(méi)有permissionList,真正的工作開(kāi)始了 */
store.dispatch('permission/FETCH_PERMISSION').then(() => {
next({ path: to.path })
})
} else {
if (to.path !== '/login') {
next()
} else {
next(from.fullPath)
}
}
}
})
來(lái)看一下 store.dispatch('permission/FETCH_PERMISSION') 都干了什么
actions: {
async FETCH_PERMISSION({ commit, state }) {
/* 獲取后臺(tái)給的權(quán)限數(shù)組 */
let permissionList = await fetchPermission()
/* 根據(jù)后臺(tái)權(quán)限跟我們定義好的權(quán)限對(duì)比,篩選出對(duì)應(yīng)的路由并加入到path=''的children */
let routes = recursionRouter(permissionList, dynamicRouter)
let MainContainer = DynamicRoutes.find(v => v.path === '')
let children = MainContainer.children
children.push(...routes)
/* 生成左側(cè)導(dǎo)航菜單 */
commit('SET_MENU', children)
setDefaultRoute([MainContainer])
/* 初始路由 */
let initialRoutes = router.options.routes
/* 動(dòng)態(tài)添加路由 */
router.addRoutes(DynamicRoutes)
/* 完整的路由表 */
commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
}
}
首先,await fetchPermission()獲取后臺(tái)給的權(quán)限數(shù)組,格式大概如下
{
"code": 0,
"message": "獲取權(quán)限成功",
"data": [
{
"name": "訂單管理",
"children": [
{
"name": "訂單列表"
},
{
"name": "生產(chǎn)管理",
"children": [
{
"name": "生產(chǎn)列表"
}
]
},
{
"name": "退貨管理"
}
]
}
]
}
其次根據(jù)我們寫(xiě)好的路由數(shù)組,進(jìn)行對(duì)比,過(guò)濾得到我們要的路由
/* 這里是我們寫(xiě)好的需要權(quán)限判斷的路由 */
const dynamicRoutes = [
{
path: '/order',
component: Order,
name: 'order-manage',
meta: {
name: '訂單管理'
},
children: [
{
path: 'list',
name: 'order-list',
component: OrderList,
meta: {
name: '訂單列表'
}
},
{
path: 'product',
name: 'product-manage',
component: ProductManage,
meta: {
name: '生產(chǎn)管理'
},
children: [
{
path: 'list',
name: 'product-list',
component: ProductionList,
meta: {
name: '生產(chǎn)列表'
}
},
{
path: 'review',
name: 'review-manage',
component: ReviewManage,
meta: {
name: '審核管理'
}
}
]
},
{
path: 'returnGoods',
name: 'return-goods',
component: ReturnGoods,
meta: {
name: '退貨管理'
}
}
]
}
]
export default dynamicRoutes
為了對(duì)比,我寫(xiě)好了一個(gè)遞歸函數(shù),用name和meta.name進(jìn)行對(duì)比 ,根據(jù)這個(gè)函數(shù)就可以得到我們想要的結(jié)果
/**
*
* @param {Array} userRouter 后臺(tái)返回的用戶權(quán)限json
* @param {Array} allRouter 前端配置好的所有動(dòng)態(tài)路由的集合
* @return {Array} realRoutes 過(guò)濾后的路由
*/
export function recursionRouter(userRouter = [], allRouter = []) {
var realRoutes = []
allRouter.forEach((v, i) => {
userRouter.forEach((item, index) => {
if (item.name === v.meta.name) {
if (item.children && item.children.length > 0) {
v.children = recursionRouter(item.children, v.children)
}
realRoutes.push(v)
}
})
})
return realRoutes
}
得到過(guò)濾后的數(shù)組后,加入到path為''的children下面
{
path: '',
component: Layout,
name: 'container',
redirect: 'home',
meta: {
requiresAuth: true,
name: '首頁(yè)'
},
children: [
{
path: 'home',
component: Home,
name: 'home',
meta: {
name: '首頁(yè)'
}
},
<!-- 將上面得到的東西加入到這里 -->
...
]
}
這個(gè)時(shí)候,path為''的children就是我們左側(cè)的導(dǎo)航菜單了,存到state的sidebarMenu待用。加入到children后,這時(shí)DynamicRoutes就可以加入到路由了。
/* 動(dòng)態(tài)添加路由 */
router.addRoutes(DynamicRoutes)
/* 初始路由 */
let initialRoutes = router.options.routes
/* 合并起來(lái),就是完整的路由了 */
commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
路由添加完了,也就是action操作完畢了,即可在action.then里面調(diào)用 next({ path: to.path })進(jìn)去路由,這里要注意, next里面要傳參數(shù)即要進(jìn)入的頁(yè)面的路由信息,因?yàn)閚ext傳參數(shù)后,當(dāng)前要進(jìn)入的路由會(huì)被廢止,轉(zhuǎn)而進(jìn)入?yún)?shù)對(duì)應(yīng)的路由,雖然是同一個(gè)路由,這么做主要是為了確保addRoutes生效了。
進(jìn)入路由后,要開(kāi)始生成左側(cè)菜單,之前我們已經(jīng)存到sidebarMenu了,現(xiàn)在需要做的只是遞歸生成菜單而已,雖然用了element的導(dǎo)航菜單,但是為了遞歸路由,還需要自己封裝一下。這里核心的地方是組件的name,在組件里面有children的地方,又再次使用自己,從而遍歷整個(gè)tree結(jié)構(gòu)的路由。
<template>
<div class="menu-container">
<template v-for="v in menuList">
<el-submenu :index="v.name" v-if="v.children&&v.children.length>0" :key="v.name">
<template slot="title">
<i class="iconfont icon-home"></i>
<span>{{v.meta.name}}</span>
</template>
<el-menu-item-group>
<my-nav :menuList="v.children"></my-nav>
</el-menu-item-group>
</el-submenu>
<el-menu-item :key="v.name" :index="v.name" @click="gotoRoute(v.name)" v-else>
<i class="iconfont icon-home"></i>
<span slot="title">{{v.meta.name}}</span>
</el-menu-item>
</template>
</div>
</template>
<script>
export default {
name: 'my-nav',
props: {
menuList: {
type: Array,
default: function() {
return []
}
}
},
methods: {
gotoRoute(name) {
this.$router.push({ name })
}
}
}
</script>
刷新頁(yè)面后,根據(jù)我們r(jià)outer.beforeEach的判斷,有token但是沒(méi)permissionList,我們是會(huì)重新觸發(fā)action去獲取路由的,所以無(wú)需擔(dān)心。但是導(dǎo)航菜單active效果會(huì)不見(jiàn)。不過(guò)我們已經(jīng)把el-menu-item的key設(shè)置為路由的name,那么我們只要在刷新后,在afterEach把當(dāng)前路由的name賦值給el-menu default-active即可。同理,在afterEach階段獲取所有matched的路由,即可實(shí)現(xiàn)面包屑導(dǎo)航。
if (!store.state.permission.permissionList) {
store.dispatch('permission/FETCH_PERMISSION').then(() => {
next({ path: to.path })
})
}
...
router.afterEach((to, from, next) => {
var routerList = to.matched
store.commit('setCrumbList', routerList)
store.commit('permission/SET_CURRENT_MENU', to.name)
})
退出登陸后,需要刷新頁(yè)面,因?yàn)槲覀兪峭ㄟ^(guò)addRoutes添加的,router沒(méi)有deleteRoutes這個(gè)api,所以清除token,清除permissionList等信息,刷新頁(yè)面是最保險(xiǎn)的。
最后還有一點(diǎn),每次請(qǐng)求得帶上token, 可以對(duì)axios封裝一下來(lái)處理
var instance = axios.create({
timeout: 30000,
baseURL
})
// 添加請(qǐng)求攔截器
instance.interceptors.request.use(
function(config) {
// 請(qǐng)求頭添加token
if (store.state.UserToken) {
config.headers.Authorization = store.state.UserToken
}
return config
},
function(error) {
return Promise.reject(error)
}
)
/* axios請(qǐng)求二次封裝 */
instance.get = function(url, data, options) {
return new Promise((resolve, reject) => {
axios
.get(url, data, options)
.then(
res => {
var response = res.data
if (response.code === 0) {
resolve(response.data)
} else {
Message.warning(response.message)
/* reject(response.message) */
}
},
error => {
if (error.response.status === 401) {
Message.warning({
message: '登陸超時(shí),請(qǐng)重新登錄'
})
store.commit('LOGIN_OUT')
window.location.reload()
} else {
Message.error({
message: '系統(tǒng)異常'
})
}
reject(error)
}
)
.catch(e => {
console.log(e)
})
})
}
export default instance
總結(jié)
以上所述是小編給大家介紹的基于vue,vue-router, vuex及addRoutes進(jìn)行權(quán)限控制問(wèn)題,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- vue-router結(jié)合vuex實(shí)現(xiàn)用戶權(quán)限控制功能
- vue router+vuex實(shí)現(xiàn)首頁(yè)登錄驗(yàn)證判斷邏輯
- 基于Vue、Vuex、Vue-router實(shí)現(xiàn)的購(gòu)物商城(原生切換動(dòng)畫(huà))效果
- vue-router+vuex addRoutes實(shí)現(xiàn)路由動(dòng)態(tài)加載及菜單動(dòng)態(tài)加載
- Vue-router 類似Vuex實(shí)現(xiàn)組件化開(kāi)發(fā)的示例
- 詳解使用Vue Router導(dǎo)航鉤子與Vuex來(lái)實(shí)現(xiàn)后退狀態(tài)保存
- Vuex與Vue router的使用詳細(xì)講解
相關(guān)文章
Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流
本文主要介紹了Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12
vue使用this.$message不生效的部分原因及解決方案
這篇文章主要介紹了vue使用this.$message不生效的部分原因及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
vue中的vue-print-nb如何實(shí)現(xiàn)頁(yè)面打印
這篇文章主要介紹了vue中的vue-print-nb如何實(shí)現(xiàn)頁(yè)面打印,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue監(jiān)聽(tīng)數(shù)據(jù)的原理詳解
本篇文章主要介紹了Vue監(jiān)測(cè)數(shù)據(jù)的原理,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看2021-10-10
淺談vue同一頁(yè)面中擁有兩個(gè)表單時(shí),的驗(yàn)證問(wèn)題
今天小編就為大家分享一篇淺談vue同一頁(yè)面中擁有兩個(gè)表單時(shí),的驗(yàn)證問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
從Vue轉(zhuǎn)換看Webpack與Vite 代碼轉(zhuǎn)換機(jī)制差異詳解
這篇文章主要為大家介紹了從Vue轉(zhuǎn)換看Webpack與Vite代碼轉(zhuǎn)換機(jī)制差異詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10

