这段时间在研究element-admin,感受这个库有许多值得学习的地方,我学习这个库的方法是,先看它的路由,顺着路由,摸清它的逻辑,有点像顺藤摸瓜。css
这个库分的模块很是清晰,适合多人合做开发项目,可是若是一我的去用这个库开发后台,步骤显的有点繁琐,特别是调用后端接口,以前一个方法就调用到了,可是用这个库,须要前后分几步调用。html
好比说调用一个登录接口:点击登录按钮----》调用store中的方法----》调用api中对应登录的方法---》request.js中封装的axios方法vue
4步!!!!,让我看来确实是有点繁琐,这个问题到后面解决,经过本身封装的axios方法,直接调用后台接口,目前不知道会不会遇到其它问题。好了,接下来进入正题!!!ios
接下来先介绍一下,element-admin的登陆逻辑git
一、先看登陆方法里写什么:es6
handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { this.loading = true; //调用user模块红的login console.log("点击登录按钮") this.$store.dispatch('user/login', this.loginForm).then(() => { console.log("登陆成功"); this.$router.push({ path: this.redirect || '/' }); this.loading = false; }).catch(() => { this.loading = false; }) } else { console.log('error submit!!'); return false; } }) }
经过上面红色代码,能够看出,点击过登陆按钮后,调用了$store里的一个方法,名叫logingithub
二、下面来看看$store里的这个login方法:vue-router
const actions = { // user login login({ commit }, userInfo) { const { username, password } = userInfo; return new Promise((resolve, reject) => { console.log("vuex中的请求") login({ username: username.trim(), password: password }).then(response => { console.log('vuex中') console.log(response); const { data } = response; commit('SET_TOKEN', data.token);//存在vueX中 setToken(data.token);//存在cookie中 resolve(); }).catch(error => { console.log(error); reject(error); }) }) },
咿,怎么两个login,熟悉vuex的话应该知道,第一个login是$store中的方法,第二个login方法是,api里的login方法,用来调用接口的vuex
三、再来看看api中的login方法:element-ui
import request from '@/utils/request' export function login(data) { return request({ url: '/user/login', method: 'post', data }) }
上面是api中的login方法,它调用了request.js,request.js是封装了axios,是用来请求后台接口的,若是这个接口请求成功了,就回到了第一步中的.then()方法中
代码是,路由跳转到首页,进入正式页面!!!
重点来了!!!
之因此是称之为权限,也就是必须知足必定的条件才可以访问到正常页面,那么若是不知足呢?若是我没有token,让不让我进正常页面呢??
确定是不让的,那我没有token,该去哪?答案是还待在登陆页,哪都去不了,那么这些处理应该在哪写呢?答案是,permission.js模块
这个js在main.js引入,其实就是个路由拦截器:来看看它的代码:
import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' // progress bar 一个进度条的插件 import 'nprogress/nprogress.css' // progress bar style import { getToken } from '@/utils/auth' // get token from cookie import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) // NProgress Configuration 是否有转圈效果 const whiteList = ['/login'] // 没有重定向白名单 router.beforeEach(async(to, from, next) => { // 开始进度条 NProgress.start() // 设置页面标题 document.title = getPageTitle(to.meta.title) // 肯定用户是否已登陆 const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // 若是已登陆,则重定向到主页 next({ path: '/' }) NProgress.done() } else { const hasGetUserInfo = store.getters.name; console.log(hasGetUserInfo); if (hasGetUserInfo) { console.log("有用户信息"); next(); } else { console.log("无用户信息") try { // 得到用户信息 await store.dispatch('user/getInfo'); //实际是请求用户信息后返回,这里是模拟数据,直接从store中取 const roles=store.getters.roles; store.dispatch('permission/GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表 router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表 router.options.routes=store.getters.routers; next({ ...to, replace: true });// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record }) // next() } catch (error) { // 删除token,进入登陆页面从新登陆 await store.dispatch('user/resetToken'); Message.error(error || 'Has Error'); next(`/login?redirect=${to.path}`); NProgress.done(); } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // 在免费登陆白名单,直接去 next() } else { // 没有访问权限的其余页面被重定向到登陆页面。 next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // 完成进度条 NProgress.done() })
一看代码好多,不过不要怕,我来分析一下它的状况,不就是点if else嘛
从上面代码看,每次路由跳转,都要从cookie中取token,
那么能够分两种状况,有token和无token
有token:再看看是否是去登陆页的,登陆页确定不能拦截的,若是是登陆页就直接放行。若是不是登陆页,就要看看本地有没有用户信息,看看cookie中有没有用户信息(不必定是token,也多是localstorage)。若是有用户信息,放行。若是没有用户信息,就调用接口去获取登陆信息,而后后面还有一点代码,涉及到了动态添加路由(这里先说到这,后面具体说动态添加权限路由的事)。获取到用户信息后放行。若是在获取用户信息的过程当中报错,则回到登陆页
无token:先看看用户要进入的页面是否是在白名单内,通常登陆、注册、忘记密码都是在白名单内的,这些页面,在无token的状况下也是直接放行。若是不在白名单内,滚回登陆页。
以上就是element-admin的登陆逻辑了。不知道可否帮助到你,可是写下来,让本身的思路更清晰,也是不错的。
下面来讲一下,element-admin的动态权限路由,显示侧边栏是什么逻辑
首先要了解一下,侧边栏是如何渲染出来的,看看layout/components/slibar/index.vue有这样一段代码:
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
计算属性中有这样一段代码:
routes() { return this.$router.options.routes },
这个routes,是路由的元信息!!!是一个数组
看到这就应该明白,侧边栏是如何渲染出来的,
再来看看router.js里的代码:
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) /* Layout */ import Layout from '@/layout' /** * 注意: 子菜单只在路由子菜单时出现。长度> = 1 * 参考网址: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html * * hidden: true 若是设置为true,项目将不会显示在侧栏中(默认为false) * alwaysShow: true 若是设置为true,将始终显示根菜单 * 若是不设置alwaysShow, 当项目有多个子路由时,它将成为嵌套模式,不然不显示根菜单 * redirect: noRedirect 若是设置noRedirect,则不会在面包屑中重定向 * name:'router-name' the name is used by <keep-alive> (must set!!!) * meta : { roles: ['admin','editor'] 控制页面角色(能够设置多个角色) title: 'title' 名称显示在侧边栏和面包屑(推荐集) icon: 'svg-name' 图标显示在侧栏中 breadcrumb: false 若是设置为false,则该项将隐藏在breadcrumb中(默认为true) activeMenu: '/example/list' 若是设置路径,侧栏将突出显示您设置的路径 } */ /** * constantRoutes * 没有权限要求的基本页 * 全部角色均可以访问 * 不须要动态判断权限的路由 */ export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index'), hidden: true }, { path: '/404', component: () => import('@/views/404'), hidden: true }, { path: '/', component: Layout, redirect: '/self', children: [{ path: 'self', name: 'Self', component: () => import('@/views/self/index'), meta: { title: '首页', icon: 'dashboard' } }] }, { path: '/example', component: Layout, redirect: '/example/table', name: 'Example', meta: { title: 'Example', icon: 'example' }, children: [ { path: 'table', name: 'Table', component: () => import('@/views/table/index'), meta: { title: 'Table', icon: 'table'} }, { path: 'tree', name: 'Tree', component: () => import('@/views/tree/index'), meta: { title: 'Tree', icon: 'tree',breadcrumb: true}, hidden: false,//在侧边栏上显示 true为不显示 当父路由的字路由为1个时,不显示父路由,直接显示子路由 alwaysShow:false,//默认是false 设置为true时会忽略设置的权限 一致显示在跟路由上 } ] }, { path: '/form', component: Layout, children: [ { path: 'index', name: 'Form', component: () => import('@/views/form/index'), meta: { title: 'Form', icon: 'form' } } ] }, { path: '/nested', component: Layout, redirect: '/nested/menu1', name: 'Nested', meta: { title: 'Nested', icon: 'nested' }, children: [ { path: 'menu1', component: () => import('@/views/nested/menu1/index'), // Parent router-view name: 'Menu1', meta: { title: 'Menu1' }, children: [ { path: 'menu1-1', component: () => import('@/views/nested/menu1/menu1-1'), name: 'Menu1-1', meta: { title: 'Menu1-1' } }, { path: 'menu1-2', component: () => import('@/views/nested/menu1/menu1-2'), name: 'Menu1-2', meta: { title: 'Menu1-2' }, children: [ { path: 'menu1-2-1', component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'), name: 'Menu1-2-1', meta: { title: 'Menu1-2-1' } }, { path: 'menu1-2-2', component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'), name: 'Menu1-2-2', meta: { title: 'Menu1-2-2' } } ] }, { path: 'menu1-3', component: () => import('@/views/nested/menu1/menu1-3'), name: 'Menu1-3', meta: { title: 'Menu1-3' } } ] }, ] }, { path: 'external-link', component: Layout, children: [ { path: 'https://panjiachen.github.io/vue-element-admin-site/#/', meta: { title: 'External Link', icon: 'link' } } ] }, { path: '/self', component: Layout, children: [ { path: 'index', name: 'self', component: () => import('@/views/self'), meta: { title: 'self', icon: 'form' } } ] }, // 404页面必须放在最后!! // { path: '*', redirect: '/404', hidden: true } ] // 建立路由 const createRouter = () => new Router({ // mode: 'history', // 须要服务支持 scrollBehavior: () => ({ y: 0 }), routes: constantRoutes }) var router = createRouter() // 重置路由 // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // reset router } //异步挂载的路由 //动态须要根据权限加载的路由表 export const asyncRouterMap = [ { path: '/permission', component: Layout, name: 'permission', redirect: '/permission/index222', meta: {title:'permission', role: ['admin','super_editor'] }, //页面须要的权限 children: [ { path: 'index222', component: () => import('@/views/self'), name: 'index222', meta: {title:'权限测试1',role: ['admin','super_editor'] } //页面须要的权限 }, { path: 'index333', component: () => import('@/views/self'), name: 'index333', meta: {title:'权限测试2',role: ['admin','super_editor'] } //页面须要的权限 } ] }, { path: '*', redirect: '/404', hidden: true } ]; export default router
注意以上代码中红色的代码,这个routes中分两块路由配置,一块是固定的,无权限的路由配置,也就是不论是管理员身份,仍是超级管理员身份,都会显示的路由配置。
第二块是,带权限的路由配置,根据用户权限来显示侧边栏。注意,带权限的配置里的meta中都有role项,表明是权限
首先,咱们在获取用户信息时,会获得这个用户有哪些权限,是一个数组,假如是这样的
commit('SET_ROLES',['admin','super_editor']);//自定义权限
这个用户的权限有这些(admin、super_editor),而后再根据用户权限来筛选出符合的动态添加的路由,
何时筛选呢?
这就用到登陆时的拦截器了,上面遇到过,就在哪执行,来看看那里都是写了一些什么代码:
拿到这看看:
// 得到用户信息 await store.dispatch('user/getInfo'); //实际是请求用户信息后返回,这里是模拟数据,直接从store中取 const roles=store.getters.roles; store.dispatch('permission/GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表 router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表 router.options.routes=store.getters.routers; next({ ...to, replace: true });// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record })
routes其实就是上面的两个权限组成的数组,而后传入了GenerateRoutes方法内,(注意es6语法,看不懂的去了解一下es6),再看看GenerateRoutes中的代码:
import { asyncRouterMap, constantRoutes } from '@/router'; function hasPermission(roles, route) { if (route.meta && route.meta.role) { return roles.some(role => route.meta.role.indexOf(role) >= 0) } else { return true } } const permission = { namespaced: true, state: { routers: constantRoutes, addRouters: [] }, mutations: { SET_ROUTERS: (state, routers) => { state.addRouters = routers; state.routers = constantRoutes.concat(routers); } }, actions: { GenerateRoutes({ commit }, data) {//roles是用户所带的权限 return new Promise(resolve => { const { roles } = data; const accessedRouters = asyncRouterMap.filter(v => { // if (roles.indexOf('admin') >= 0) { // return true; // }; if (hasPermission(roles, v)) { if (v.children && v.children.length > 0) { v.children = v.children.filter(child => { if (hasPermission(roles, child)) { return child } return false; }); return v } else { return v } } return false; }); commit('SET_ROUTERS', accessedRouters); resolve(); }) } } }; export default permission;
GenerateRoutes方法办了一件事,就是把动态路由配置里符合用户权限的配置筛选出来,组成一个数组,而后,和固定路由合并到了一块儿,存到了vuex中,
而后调用了这两句代码:
router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表 router.options.routes=store.getters.routers;
router.addRoutes()方法是,动态添加路由配置,参数是符合路由配置的数组,
而后将路由元信息,变成合并后的路由元信息,由于渲染侧边栏须要用到,
这两句代码,顺序不能变,缺一不可,缺了addRoutes,点击侧边栏的动态路由会无反应,缺了options,侧边栏显示不出来动态添加的路由!!!!
以上就是element-admin的动态路由了,睡觉。。。。
今天正式开始用这个模板开发了,想说一下,这个模板有许多模块,我用不到,好比说mock文件,当咱们不用它自己的api去请求接口时,彻底能够将它删除,我是今天把它原有的登陆逻辑去掉了,登陆时,就不会有mock的事了。
因而,我一个一个文件去注释,对运行不影响的都删除了,test文件一下删除了,还有些不知道是干什么的js也去了,这样项目看着对我来讲比较轻巧,也比较熟悉。另外,哪些utils。还有store里不用的方法均可以去掉。
当我把登陆改了以后,能去掉一大堆东西好很差。ok,有时间整理一个轻巧的模板,挂到码云上
更新一个bug,目前页面上的路由拦截器的逻辑,并非我如今用的逻辑,不过相差不远,项目上线后,发现刷新动态加载的路由页面,页面白屏,调试一下,发现是路由拦截器在重复跳转页面,这问题很奇怪,
本地没有问题,而后琢磨了半天,既然是重复跳转,它有一个特征就是:to.path==from.path;那么能够利用这一特质,去拦截这种状况,
if(to.path==from.path){ next(); return; }
监听浏览器前进后退事件
window.addEventListener("popstate", function () { backStatus=true; return; })