------------------------------------------------------------------------------------------------
不搬砖你养我啊
------------------------------------------------------------------------------------------------
流程图
相信大部分同学在做路由权限处理的时候都是从 @花裤衩 大佬的手摸手系列借鉴来的, 我在做这个模块的时候也是从大佬仓库copy过来改改逻辑,因为仓库项目代码太多和每个公司实现权限鉴别的方式不一致, 所以写个demo记录一下。
关于权限控制方式
手摸手,带你用vue撸后台 系列一(基础篇) 实现权限控制的方式是完全通过前端来控制, 后端只需要返回登录者的 role (角色身份) , 然后通过role去动态生成可以访问的路由。 但有的公司是登录之后后端直接返回可访问路由表由前端去匹配。
具体怎么控制还得看公司的业务需求, 本demo通过后端直接返回路由表进行匹配
demo实现过程
demo越简单越好
mock服务
在package.json文件配置mock脚本
(因为node不能直接运行ts代码, 引入ts-node-dev插件)
"scripts": { "mock": "cd mock && ts-node-dev mock-server.ts", }
使用express起一个本地服务用来获取可访问路由表(mock =>mock-server.ts)
import express from 'express'
import Mock from 'mockjs'
import bodyParser from 'body-parser'
const app = express()app.use(bodyParser.json())
const pageRouter = require('./page-router')
// 设置跨域
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
// 此处根据前端请求携带的请求头进行配置
res.header('Access-Control-Allow-Headers','Content-Type,Access-Token,X-Access-Token' )
next()
})
// ******************* 获取路由表 *************************
app.get('/router', (req, res) => {
res.json(Mock.mock(pageRouter.getPageRouter(req)))
})
app.listen('3000', () => { console.log('mock服务器启动ing中... port: 3000')})
根据登录用户生成该用户可访问的路由表(admin,operator: 可看作是用户名)
getPageRouter: (data: any) => {
const resTemp: IResTemp = {
status: {
code: 0,
message: 'success',
time: Random.now('day', 'yyyy-MM-dd HH:mm:ss')
},
data: {
userType: '',
dataList: []
}
}
if (data.query.type === 'admin') {
resTemp.data.userType = 'admin'
resTemp.data.dataList = adminRouter
} else if (data.query.type === 'operator') {
resTemp.data.userType = 'operator'
resTemp.data.dataList = operatorRouter
} else {
resTemp.status.code = 403
resTemp.status.message = '无法登录,请联系管理员'
resTemp.data.dataList = []
}
return resTemp
}
}
tip:
mock数据时如果改动mock文件里面的代码需要重启服务, 可以使用 “nodemon” 来实现类似热更新的功能
路由文件
在做动态路由匹配之前需要预先定义好静态路由(constantRoutes)和动态路由(asyncRoutes)
静态路由指不需要权限即可访问,例如登录页,404页面等。动态路由从后端获取,需要注意的是路由为 “*”的这个特殊路由需要放在动态路由的最后
参考vue官方文档: 动态路由匹配
在router.ts文件暴露一个路由初始化的函数 resetRouter
退出登录或登录信息过期时清除动态路由信息
const createRouter = () =>
new Router({
mode: 'history',
scrollBehavior: () => ({ x: 0, y: 0 }),
routes: constantRoutes
})
const router: any = createRouter()
export function resetRouter() {
const newRouter: any = createRouter()
router.matcher = newRouter.matcher // reset router
}
动态路由匹配
src => permission.ts
import router from './router'
import { Route } from 'vue-router'
import { UserModule } from '@/store/modules/userInfo'
import { asyncRoutes, constantRoutes } from '@/router'
// 动态路由匹配(根据业务逻辑而定)
const formatAsyncRoutes = ( apiRoutes: ApiRoute[], asyncRoutes: LocalRoute[]) => {
const res: LocalRoute[] = []
apiRoutes.map(item1 => {
asyncRoutes.map(item2 => {
if (item1.path === item2.path) {
const temp = item2
if (item1.children && item2.children) {
temp.children = formatAsyncRoutes(item1.children, item2.children)
}
res.push(temp)
}
})
})
return res
}
const setAsyncRoutes = (apiRoutes: ApiRoute[], asyncRoutes: LocalRoute[]) => {
const accessRoutes = formatAsyncRoutes(apiRoutes, asyncRoutes)
accessRoutes.push({ path: '*', redirect: '/404', meta: { hidden: true } })
router.addRoutes(accessRoutes)
// 手动添加动态路由(侧边栏使用)
router.options.routes = constantRoutes.concat(<any[]>accessRoutes)
}
// 在路由上挂载添加动态路由的方法
router.setAsyncRoutes = setAsyncRoutes
router.beforeEach(async (to: Route, from: Route, next: any) => {
if (to.path === '/login') {
next()
} else {
const hasRoute = UserModule.userInfo.userRoutes.length
if (hasRoute) {
next()
} else {
// 防止页面刷新数据丢失
const userInfo = localStorage.getItem('userInfo')
if (userInfo) {
const formatUserInfo: LoginUserInfo = {
userType: JSON.parse(userInfo).userType,
userRoutes: JSON.parse(userInfo).dataList
}
UserModule.SET_USER_INFO(formatUserInfo)
setAsyncRoutes(<ApiRoute[]>UserModule.userInfo.userRoutes, asyncRoutes)
next({ ...to, replace: true })
} else {
next('/login')
}
}
}})
因为通常情况下后端返回的数据结构跟前端路由表结构有所出入,所以很有必要定义一个函数来格式化路由
通配路由必须挂在动态路由的结尾
accessRoutes.push({ path: '*', redirect: '/404', meta: { hidden: true } })
在demo中使用 router.options来生成侧边栏,所以需要手动添加动态路由信息
router.options.routes = constantRoutes.concat(<any[]>accessRoutes)
在路由实例上面挂载一个设置动态路由的方法共全局使用(登录时)
router.setAsyncRoutes = setAsyncRoutes
router.beforeEach: 第一次进入页面的时候会执行一次动态路由, 具体逻辑可参照上面流程图
登录
src => store =>modules => userInfo.ts
登录之后调用在permisson.ts挂在载Router上的 setAsyncRoutes 方法进行
import router, { resetRouter, asyncRoutes } from '@/router'import { getPageRouter } from '@/api/page-router/page-router.ts'
@Action
Login (userType: string) {
return new Promise(resolve => {
getPageRouter({ type: userType }).then(res => {
localStorage.setItem('userInfo', JSON.stringify(res.data))
this.SET_USER_INFO({
userType: res.data.userType,
userRoutes: res.data.dataList
})
router.setAsyncRoutes(<ApiRoute[]>this.userInfo.userRoutes, asyncRoutes)
resolve()
})
})
}
退出登录
src => store =>modules => userInfo.ts
import router, { resetRouter, asyncRoutes } from '@/router'
@Mutation REMOVE_USER_INFO () {
localStorage.removeItem('userInfo')
this.userInfo = {
userType: '',
userRoutes: []
}
router.push('/login')
resetRouter()
}
侧边栏
src => layout => components => Sidebar => SidebarItem.vue
通过递归这个组件生成,没啥好说的 - -
关于其他
demo中只做了页面级的权限控制, 还有按钮级别的权限控制, 有兴趣的同学可以自己去尝试(可以根据 vue的指令配合后端接口来做)
最后放上demo的地址