vue-element-admin 登陆
引言
vue-element-admin是vue生态圈中,最火的一个后台管理框架。基于vue和element-ui实现。
这篇文章主要会讲解登陆的流程以及我认为这个框架的厉害的东西:动态路由,之前看代码的时候,总想着一个登陆搞那么麻烦,后面仔细品味发现原来一个小小的登陆功能涉及到了这么多的东西。
准备工作
目录结构
了解一个框架之前,先要从目录结构入手(这里直接引用花裤衩大佬的目录结构)。目录结构的了解能够更加清楚模块功能的划分。
├── src # 源代码│ ├── api # 所有的api请求│ ├── assets # 主题 字体等静态资源│ ├── components # 全局公用组件│ ├── directive # 全局指令│ ├── filters # 全局 filter过滤器│ ├── icons # 项目所有 svg icons│ ├── layout # 全局 layout│ ├── router # 路由│ ├── store # 全局 store管理│ ├── styles # 全局样式│ ├── utils # 全局公用方法│ ├── vendor # 公用vendor│ ├── views # views 所有页面│ ├── App.vue # 入口页面│ ├── main.js # 入口文件 加载组件 初始化等│ └── permission.js # 权限管理├── tests # 测试├── .env.xxx # 环境变量配置├── .eslintrc.js # eslint 配置项├── .babelrc # babel-loader 配置├── .travis.yml # 自动化CI配置├── vue.config.js # vue-cli 配置├── postcss.config.js # postcss 配置└── package.json # package.json复制代码
permission.js
在登陆这个流程中,permission.js这个是最重要的一环,其实这个文件就是路由的全局钩子(beforeEach和afterEach),全局钩子的意思就是每次跳转的时候可以根据情况进行拦截,不让它进行跳转。使用场景最常见的就是有些页面需要用户登陆之后才能访问,就可以在beforeEach中校验用户是否登陆来进行相对应的拦截处理。下面会详细的讲解permission.js的内容。
util / auth.js
这个文件主要就是设置token到cookie中的操作封装。
router
这个是路由中的一些设置,理解这个后面看组件Sidebar、TagViews将会事半功倍。
/** hidden: true 是否隐藏于Sidebar侧边栏 alwaysShow: true是否显示在根菜单 redirect: noRedirectBreadcrumb中重定向的path name: 'router-name'用于keep-alive的Name meta: {roles: ['admin', 'editor'],当前路由的访问所需要权限title: 'title',Sidebar和Breadcrumb的titleicon: 'svg-name',Sibebar的iconnoCache: true是否设置不缓存breadcrumb: true是否显示在Breadcrumb上activeMenu: '/example/list'Sidebar高亮时的显示path }**/复制代码
其他的一些我就没有介绍了,比如说封装好axios的request.js,还有把请求封装成api,这些可以自行去了解。
view / login / index.vue
省略了一些的细枝末节,直接从点击登陆之后发生了一系列事情开始讲起,第一个就是handleLogin方法。
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 || '/', query: this.otherQuery }) this.loading = false }) .catch(() => { this.loading = false }) } else { console.log('error submit!!') return false } })}复制代码
可以看到这个方法很简单,就是利用validate方法进行表单验证,验证通过则使用this.$store.dispatch调用user/login方法并传递这个表单的数据,然后有一个.then()方法,方法里面有this.$router.push(...),可能有同学就会有疑惑了,this.redirect和this.otherQuery是啥,用一句话来概括就是:我从哪里跳到/login页面,登陆之后我就返回到哪里。
这个user/login是什么呢?一起来揭开它神秘的面纱。
user / login
先解析这个之前,先来补充一点vuex基础知识:
vuex中使用namespaced:true开启命名空间,调用mutations或者调用actions,则是模块名 + 相对应的方法名。
另外actions是异步的,action处理函数之后返回的Promise进行相对应的处理。
// user.js// 不开启命名空间const actions = { login(){}}export default { actions };this.$store.dispatch('login');// user.js// 开启命名空间const actions = { login(){}}export default { actions, namespaced: true }; // 注意!开启命名空间this.$store.dispatch('user/login'); // 模块名user + 方法名 login复制代码
上面所说的user/login,则就是user模块中的login方法,核心代码就如下:
login({ commit }, userInfo) { const { username, password } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: password }).then(response => { const { data } = response // 解构出data commit('SET_TOKEN', data.token) // 更新store里面的token setToken(data.token) // token保存到cookie resolve() }).catch(error => { reject(error) }) }) }复制代码
一句话来概括:登陆验证,登陆成功之后,分别保存token到vuex、cookie中。
这里完成之后就会回到之前的view/login/index.vue,当user/login调用完成之后,则会进行.then(...)方法,就是一个路由跳转的过程(this.$router.push(...))。
Permission.js
这个文件就如同前面介绍所说,路由的全局钩子,动态路由的实现这里相当于是一个入口。来看核心的实现代码。
router.beforeEach(async(to, from, next) => { // 从cookie中取得token const hasToken = getToken() // 如果有token 也就是已经登陆的情况下 if (hasToken) { // 并且要前往的路径是'/login' 则返回 '/' if (to.path === '/login') { next({ path: '/' }) } else { // 从store中取得用户的 roles, 也就是用户的权限 并且用户的权限数组必须有一个以上 const hasRoles = store.getters.roles && store.getters.roles.length > 0 // 有权限则直接进入 if (hasRoles) { next() } else { // 没有权限的话 try { // 获取用户信息 const { roles } = await store.dispatch('user/getInfo') // 生成可访问路由 const accessRoutes = await store.dispatch('permission/generateRoutes', roles) // 将可访问路由添加到路由上 router.addRoutes(accessRoutes) // 进入路由 next({ ...to, replace: true }) } catch (error) { // 如果出现异常 清空路由 await store.dispatch('user/resetToken') // Message提示错误 Message.error(error || 'Has Error') // 跳到login页面重新登陆 next(`/login?redirect=${to.path}`) } } } } else { // 没有token 也就是没有登陆的情况下 // 判断是否是白名单(也就是说不需要登陆就可以访问的路由) if (whiteList.indexOf(to.path) !== -1) { next() } else { // 其他的一路给我跳到login页面 老老实实的进行登陆 next(`/login?redirect=${to.path}`) NProgress.done() } }})复制代码
注释已经写的明明白白了,这个思路其实使用的特别多,就是利用全局钩子进行访问的拦截,如果没有登陆的话,跳转到登陆页面进行登陆。
但是花裤衩大佬的这个有一点点不同,可以看到他将登陆和获取用户信息分成了两步,原因就是保证用户信息是最新的。第二个是在获取用户信息之后返回的roles生成可访问的路由,也就是这两步实现了动态路由。
用一句话来概括:是否登陆?没有就给我老老实实登陆。是否有用户信息?没有就给我获取用户信息,并且生成可访问路由然后利用addRoutes进行添加。
这两步都是actions:user/getInfo,permission/generateRoutes。
可以看到这是vuex中的actions提交,可能有些小伙伴会有点困惑,为什么有的请求直接写在api文件夹,有些就放在actions里面?这个问题:我的理解就是,如果返回的数据要保存在vuex中的话,可以直接使用actions,原因是action里面可以提交mutation改变store的状态,可以少写一些代码,同时思路更加清晰,如果返回的数据只需要在当前页面使用的话,并不需要保存到vuex中,那就直接用写在api文件夹中引用即可。当然这个是我的个人理解,如有错误请望指出。
user / getInfo
首先我们看第一个user/getInfo。
如上面所说,这是user模块中的getInfo方法,来看核心代码。
getInfo({ commit , state }) { return new Promise((resolve, reject) => { // 调用getInfo接口 getInfo(state.token).then(response => { const { data } = response // 解构出data if (!data) { // 进行数据校验 reject('Verification failed, please Login again.') } // 解构出需要保存的值 const { roles, name, avatar, introduction } = data // roles权限数组至少有一个权限 if (!roles || roles.length <= 0) { reject('getInfo: roles must be a non-null array!') } // 保存到vuex commit('SET_ROLES', roles) commit('SET_NAME', name) commit('SET_AVATAR', avatar) commit('SET_INTRODUCTION', introduction) // 将 data 返回 resolve(data) }).catch(error => { reject(error) }) })}复制代码
这个就是调用getInfo接口获取用户信息并且保存到vuex中,为了严谨性,进行相对应的数据校验,然后把data返回。
用一句话来概括:获取用户信息,并保存用户信息。
permission/generateRoutes
我们来看第二个actions,这个是动态路由中的重要一步,生成可访问路由,根据当前用户的权限数组,和路由中可访问的权限数组,进行匹配生成。
// 判断是否有权限function hasPermission(roles, route) { if (route.meta && route.meta.roles) { // roles.some => Array.some 相当于是只要有一个满足就为true // 判断用户的权限于当前路由访问所需要的权限是否有一个满足 // 比如说用户权限为 ['one','two'] 当前路由访问所需要权限为 ['two','three'] 那么就说明当前用户可以访问这个路由 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}// 为什么要写这里呢,因为后面的Sidebar组件与这个环环相扣const mutations = { SET_ROUTES: (state, routes) => { // 添加的路由 state.addRoutes = routes // 将vuex中的路由进行更新 state.routes = constantRoutes.concat(routes) }}const actions = { generateRoutes({ commit }, roles) { return new Promise(resolve => { let accessedRoutes // 如果roles包含 'admin' 直接可以全部访问 if (roles.includes('admin')) { accessedRoutes = asyncRoutes || [] } else { // 利用 filterAsyncRoutes 过滤出可访问的路由 accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } // 保存可访问的路由到store中 commit('SET_ROUTES', accessedRoutes) // 将可访问路由返回 resolve(accessedRoutes) }) }}复制代码
欧克,注释已经全部写好了,这个就是动态路由中重要一步,慢慢品味发现也不难,越看越觉得666。
用一句话来概括:根据得到的用户权限生成可以访问的路由。
动态路由的实现
router.addRoutes(accessRoutes)复制代码
???,没呢?
对的!动态路由的核心代码就这一句话,短小精悍,其他的都是为了完成动态路由做的一些 "准备工作" ,user/getInfo获取用户信息得到用户的roles权限数组,返回user/generateRoutes生成可访问路由,就是这么神奇的一步,实现了动态路由。
写到这里基本流程就走完了,但是还有两个注意点。
注意
为什么项目开启默认就是登陆页面
想必通过之前的讲解也应该知道了permission.js的作用了,全局路由钩子,每次路由跳转都要调用全局路由钩子(谁让它是全局的呢),原因就是当页面加载首页时也会经过全局路由钩子,而permission.js判断当前用户有没有登陆,没有登陆就直接跳转到/login页面进行登陆把,就是这么任性,啦啦啦。
动态路由显示不出来
有些小伙伴可能会改这个框架的代码,但是发现显示不出来?这也是我遇到过的一个坑,需要注意的是permission.js里面有两个条件:第一个是否登陆?第二个是否获取用户信息(判断是否获取用户信息是根据roles)?有些小伙伴可能是登陆的时候就把用户信息获取了,但是!!没有更新用户roles权限数组,所以就一直会获取用户信息,从而导致显示不出来。
完结,撒花
其实到这里整个登陆流程就已经结束了,可以看到花裤衩大佬想的很多,用户信息的获取和用户登陆的分离,根据用户权限生成可以访问的路由并添加,这些思路我觉得很厉害,还有这么多组件可以用,哈哈哈哈。
好的,我们来总结一下:
- Login/index.vue点击登陆 提交user/login的actions。
- user/login进行登陆验证,登陆成功之后保存token到vuex和cookie中。
- 回到Login/index.vue跳转路由,这时就到了permission.js
- permission.js进行判断是否登陆,是否有用户信息?没有用户信息就获取用户信息,并且保存到vuex,然后根据用户信息中的roles生成可访问路由,并通过addRoutes进行添加。
看到这里,相信你对这个框架的登陆流程已经有一定的了解了,自己再去理一遍把,啦啦啦啦,那下面这一张流程图相信你也可以看懂。
![5aa6ca4aa6aa58142b9fb24716ef389a.png](https://i-blog.csdnimg.cn/blog_migrate/03843b97f4bb9f0c1c64f8ca65973b17.jpeg)