前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表。比如我会配置一个asyncRoutes
数组,需要认证的页面在其路由的meta
中添加一个roles
字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,最后通过router.addRoutes(accessRoutes)
方式动态添加路由即可。
后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有页面路由信息返回给前端,前端再通过addRoutes
动态添加路由信息
按钮权限的控制通常会实现一个指令,例如v-permission
,将按钮要求角色通过值传给v-permission指令,在指令的moutned
钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除按钮。
类似Tabs
这类组件能不能使用v-permission
指令实现按钮权限控制?
使用v-if
路由拦截鉴权的方法:
1、路由拦截:单纯给路由加字段标识符,通过路由拦截实现
2、动态路由:通过路由的拆分另外需要后端的配合去实现的动态路由配置
路由拦截实现方式比较简单,只需要简单的在router.beforeEach中根据路由配置信息过滤页面是否有权限前往改组件,若相对于的权限不够则不前往相应的组件。
动态路由实现相对比较复杂,并且需要后端的配合,本质是路由配置表分成两部分,相应的不同用户登录的时候,是根据用户权限信息过滤筛选除路由配置表,动态添加,而用户没有权限的部分则不渲染。
动态路由的3个文件
# router.js
router.js的路由配置表可以分为两部分,公共路由以及动态权限路由,动态权限路由可以放在前端,鉴权的时候前端自己进行数组的过滤,也可以放在后端过滤
// 配置表都放在前端的
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export const defauleRoute = [ //固定部分权限的数组,所有用户都能访问的路由
{
path:'/login',
component:aa
},
]
export const asyncRoute = [ //动态配置的路由表,工作之前需要过滤
{
path:'/order',
name:'order',
component:aa,
meta:{
system:'order'
}
}
{
path:'/roles',
name:'roles',
component:aa,
meta:{
system:'roles'
}
}
]
//注册路由工作表
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
//重置路由工作表,退出登录的时候需要调用此方法
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router
# permission.js
主要是做全局的路由拦截,以及路由根据用户权限动态过滤等功能
主要涉及:什么时候去处理动态路由、什么条件去处理路由
import router from './router'
import store from './store'
import { getToken } from '@/utils/auth' // 自定义封装存取token的方法
Route.beforeEach((to,from,next) =>{
//取token,判断用户是否已登录
const hasToken = getToken()
if(hasToken ){
//判断用户已登录
if(to.path === "/login"){
/**
*用户已经登陆,但是还路由到login页面,这代表用户已经执行了退出登录的操
*作,所以这个地方可以清一下token之类的,或者自定义写一些逻辑
*/
next()
}else{
/**
*这里是已经登录或者点击了登录按钮,token已经存入localstorage,但是但是不路
*由到login的情况如果没有路由到/login,那么就直接让他放行就行,在这里面我处
*理一些东西,就是用户既然已经登陆,并且可以直接放行,那么我们放
*行之前,在这个地方需要做一些逻辑,就是判断用户的权限,然后根
*据用户的权限,把我们的动态配置的路由表中符合他权限的那几条路
*由给过滤出来,然后插入到路由配置表中去使用
*
*那么就涉及到两个问题:
*1:什么时候去处理动态路由(登陆之后,进入到首页之前,也就
*是next之前)
*2:什么条件处理动态路由
*/
/**
*这地方可以先判断一下store中的用户权限列表长度是否为0,若长度为0,则代表用户
*是刚点击了登录按钮,但是还没有进入到页面,这时候需要再去做一些权限过滤之类的
*操作如果长度不为0代表鉴权流程都没问题了,直接前往对应的组件就行
*这一步主要是为了防止重复过滤数组,节约性能
*/
if(store.getters.roles.length > 0){
next()
}else{
//代码如果走到了这个地方,代表用户是已登录。并且鉴权流程还没走,
//那么在这地方就需要去走鉴权流程
store.dispatch('getRoles').then(res=>{
//这个地方的res是第三步那个地方的peomise中的resolve传
//过来的,也就是权限信息的那个数组
store.dispatch('createRouters',res.data)
.then(res=>{
//这里是调用store创造动态路由的那个函数,这个地方可以把那
//个权限数组传到这个函数内部,或者不在这里传,这个
//函数内部直接去取自己state里面的roles的值也是一样的
let addRouters = store.getters('addRouters')
let allRouters = store.getters('allRouters')
//添加动态路由部分到工作路由
router.addRoutes(accessRoutes)
//vue/cli4及之后的版本,addRoutes被废弃,取而代之的是
//addRoute('paramentsName',route对象)或者
//addRoute(route对象),注意,传进去的参数不再是一个数组,
//而是每一条路由对象,如果套添加多条,那么就需要遍历添加
//如果是传两个参数的写法,那么第一个参数是父路由的名字
//这样就是把这条路由加入到父路由的children里面去
//前往拦截的页面
next({ ...to, replace: true })
//addRoute添加完路由之后一定要通过这种方式做路由跳转,
//不然可能会导致addRoute之后的第一次跳到addRoutes添加的页
//面的时候,可能是空白页面
//{...to}:作用:把整个路由对象传过去做路由跳转,包括带上
//components那些,这样就能保证第一次就能渲染到这个页面
//replace:true:本次路由记录不被浏览器所记录
})
})
}
}
} else {
/**这里是处理没有token的情况,也就是说这时候用户还没有登陆,那
*如果没用户登录,那么判断用户是不是去登录页面,如果是登录
*页面,就直接放行,如果没登陆就想去访问主页那种页面,就让
*他重定向到登录页面
*/
if(to.path == '/login'){
//这地方可以判断的仔细一点,不一定是去login的时候再让他直接放行,而是
//前往所有公共组件的时候,都直接让他放行
next()
}else{
next('/login')
}
}
})
addRoutes动态添加路由两个注意点: addRoutes: Vue/cli4及之后的版本,addRoutes被废弃,取而代之是addRoute('parentName',Route)或者addRoute(route对象),如果是传两个参数的写法,那么第一个参数是父路由的name这样就是把这条路由加入到父路由的children里面去,如果是要动态添加多天路由,就需要通过遍历的方式了 next({ ...to, replace: true }): addRoute首次添加完路由之后的路由跳转一定要通过这种方式做路由跳转,不然可能会导致addRoute之后的第一次跳到addRoutes添加的页面的时候,可能是空白页面
{...to}:作用:把整个路由对象传过去做路由跳转,包括带上 components那些,这样就能保证第一次前往addRoutes动态添加的就能渲染到这个页面
replace:true:本次路由记录不被浏览器所记录
# store.js
//在api文件夹中定义一个获取此用户的权限的接口,并且在这个actions中调用
import { getUserRole } from "../api/getRoles" //获取权限的接口
import { logout } from '../api/user' //用户退出登录的接口
import { resetRouter } from './router'
import { removeToken } from '@/utils/auth' // 自定义封装清除token的方法
//这个是过滤数组的方法,如果路由表是多层嵌套的,那么可以递归调用这个方法去过滤数组
//function hasPermission(roles, route) {
// if (route.meta && route.meta.roles) {
// return roles.some(role => route.meta.roles.includes(role))
// } else {
// return true
// }
//}
//export function filterAsyncRoutes(routes, roles) {
// const res = []
// routes.forEach(route => {
// const tmp = { ...route }
// if (hasPermission(roles, tmp)) {
// if (tmp.children) {
// tmp.children = filterAsyncRoutes(tmp.children, roles)
// }
// res.push(tmp)
// }
// })
//
// return res
//}
//引入默认路由以及动态路由
import { defauleRoute , asyncRouter } from '@/router'
const state = {
roles:[] //掉接口拿到的权限列表,假设数据格式为:["order","roles"],
allRouters: [], //这个是全部整合以后,最终要工作的路由
addRouters: [],//这个是根据权限动态匹配过滤出来部分的路由
}
const getters = {
/**把state中的roles存入到这个getters中,那么其他获取这个getters中的
*roles的地方,只要原本的roles发生改变,其他地方的这个roles也就会发生
*改变了,这个相当于是computed计算属性
*/
roles:state => state.roles
allRouters:state => state.allRouters
addRouters:state => state.addRouters
}
const mutations:{
/**在下面的actions里面通过commit把权限信息的数组提交到这个地方,然后
*这个地方把数组提交到state的roles
*/
SetRoute(state,router)
//这个地方的router就是根据用户权限,过滤出来的路由表
state.allRouters = defauleRoute.concat(router)
state.addRouters = router
}
//把路由权限数组存储到state
setRoles(state,value){
state.roles = value
}
}
const actions:{
//写一个获取当前登陆角色权限的请求,比如["/order","roles"],如果请求回
//来的是这样的,那么就代表这个角色的权限就是可以访问 order路由以及
//roles路由
//获取权限信息可能有两种情况:除了下面这种权限信息是一个单独的接口,
//权限信息也可能跟着用户登陆的接口就一并返回
//获取当前用户的权限信息,并且存入到state中,这个权限信息,可能跟后
//端在沟通的时候,他不会单独写成一个接口给你去请求,而是你在登陆请求
//的时候就把用户信息和这个此用户的权限信息都一次性返回给你了,那就在
//用户登陆的时候就把这个权限信息存入到这个state中,也一
//样的,目的就是要把权限信息的数组存入到state中就行
//获取roles权限方法
getRoles({commit},data){
return new Promise(resolve,reject){
//调用获取用户权限接口
getUserRole().then(res =>{
//这里返回的数据应该是一个权限信息的数组,如:["order","roles"]
//把权限信息通过mutations存入到state
commit('setRoles',res.data)
resolve(res.data)
})
}
})
//根据权限过滤数组配置表的方法
createRouters({ commit } , data ){
return new Promise((resolve,reject) =>{
let addRouters = [ ]
if(data.includes("admin"){
addRouters = asyncRouter
}else{
//项目开发中路由数组可能是多层嵌套,那么这地方需要用上面自定义的方
//法通过递归的方式去过滤,此demo就只按一层数组处理
//(filterAsyncRoutes)方法
addRouters = asyncRouter.filter(item=>{
if(data.includes(item.meta.system) ){
return item
}
})
}
//把这个匹配出来的权限路由传到mutations中,让mutations
//把这个匹配出来的路由存入到state
commit.("SetRoute",addRouters)
resolve() //这个地方要调用一下这个resolve,这样外面访可以通过
//.then拿到数组过滤成功的回调
})
},
logout({ commit }) {
return new Promise((resolve, reject) => {
logout().then(() => {
removeToken() // must remove token first
resetRouter()
commit('setRoles', [])
commit('SetRoute', [])
resolve()
}).catch(error => {
reject(error)
})
})
},
}
export default {
state,
getters,
mutations,
actions
}