前言
后台系统是基于vue-tao-admin开发的,从这个项目里学到了很多东西,正好也测试一下自己的后台系统功能,所以写一篇博客记录一下。
登录
登录功能的逻辑基本和原系统一样,首先前端获取用户名密码然后后端返回一个唯一token用于识别用户(这里我是直接用mongdb的_id充当token),之后把token存储到本地cookie的同时根据token向服务器获取用户信息,这个信息每次刷新浏览器就要重新获取。
handleLogin () {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
权限
权限对于后台系统来说很重要,本系统的实现思路和原系统也是相同的。
用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。
addroutes()
addroutes在最新版的vue-router里已经被弃用了,但是因为懒这里还是使用addroutes而不是最新的addroute。
在使用addroutes的时候遇到了一些问题,之后我去看了addroutes的源码才解决。
function addRoutes (routes) {
createRouteMap(routes, pathList, pathMap, nameMap)
}
function createRouteMap (
routes,
oldPathList,
oldPathMap,
oldNameMap
) {
// the path list is used to control path matching priority
var pathList = oldPathList || [];
// $flow-disable-line
var pathMap = oldPathMap || Object.create(null);
// $flow-disable-line
var nameMap = oldNameMap || Object.create(null);
routes.forEach(function (route) {
addRouteRecord(pathList, pathMap, nameMap, route);
});
// ensure wildcard routes are always at the end
for (var i = 0, l = pathList.length; i < l; i++) {
if (pathList[i] === '*') {
pathList.push(pathList.splice(i, 1)[0]);
l--;
i--;
}
}
return {
pathList: pathList,
pathMap: pathMap,
nameMap: nameMap
}
}
根据上面源码可以看出来addroutes方法只是根据你传入的routes数组生成pathlist然后和之前的pathlist进行合并。它并没有修改router对象里的options对象,即没有把新的路由数组和之前的路由数组合并,但是可以通过路由信息访问这几个路由。 所以在后续动态生成可访问路由时不能根据routes里的options对象直接生成,而是要手动添加。
addRoute()
最后还是用了addRoute()重构,addRoute除了一次只能传入一个路由以为和addRoutes没有太大区别
具体实现
-
首先路由表里要有两种数组,
constantRoutes和asyncRoutes,分别存储无权限路由和有权限路由,区别在于有权限路由里多了roles这一meta信息。export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index'), hidden: true }, ] export const asyncRoutes = [ { path: '/article', component: Layout, meta: { title: '文章管理' }, children: [ { path: 'article-publish', component: () => import('@/views/article/ArticleEdit.vue'), name: 'article-publish', meta: { title: '文章发布', roles: ['admin','*****'] } }, ] }, ] -
之后在设置beforeEach全局守卫,在进入每一个路由之前都进行权限判定
判定逻辑是:如果有token说明已经登录,如果有Info说明已经获取了信息,则直接跳转路由。否则去获取Info或者登录。
beforeEach的两个参数to,from分别代码要前往的路由和当前路由。 return true代表直接跳转,false代表拒绝跳转,return path则与router.replace(path)等方法类似。
```js
//permission.js
router.beforeEach(async(to, from) => {
const hasToken = getToken()
if (hasToken) {
console.log(to,from);
if (to.path === '/login') {
//重定向至首页
return '/'
} else {
const hasGetUserInfo = store.getters.roles || []
if (hasGetUserInfo.length) {
return true
} else {
try {
// get user info
await store.dispatch('user/getInfo').then(res=>{
const roles = res.data.roles
store.dispatch('permission/generateRoutes',roles).then((accessedRoutes)=>{
accessedRoutes.map(item=>{
router.addRoute(item)
})
return to // hack方法 确保addRoutes已完成
})
})
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
NProgress.done()
return `/login?redirect=${to.path}`
}
}
}
} else {
//即使没有登录也要有基础路由
store.dispatch('permission/initRoutes')
if (whiteList.indexOf(to.path) !== -1) {
return true
} else {
return true
}
}
})
```
3. 根据权限生成路由数组并存储在vuex中 ```js //store/permission.js 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
}
const state = {
routes: [],
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.routes = constantRoutes.concat(routes)
},
INIT_ROUTES:(state)=>{
state.routes = constantRoutes
}
}
const actions = {
generateRoutes({commit},roles){
return new Promise((resolve)=>{
let addRoutes = filterAsyncRoutes(asyncRoutes, roles) || []
commit('SET_ROUTES',addRoutes)
resolve(addRoutes)
})
},
initRoutes({commit}){
return new Promise((resolve)=>{
commit('INIT_ROUTES')
resolve()
})
}
}
```
4. 侧边栏加载
侧边栏是根据路由数组动态生成的,所以只需要把上一步生成的权限数组,根据计算方法返回即可。
(ps:引用vuex数据一定要用computed包裹,否则会失去响应性)
`const routes = computed(() => store.getters.routes)`
结语
到这里登录权限功能也就基本完成了,还有很多其他的功能没有实现,有时间慢慢来吧。