(1)当用户打开 localhost,此时还没有 token,匹配的是空路由,我们重定向到登录页 next({ path: '/login' });
(2)用户在登录页刷新页面,也会进入路由拦截,此时匹配的是 login 路由,而 login 路由是不需要登录验证的(requiresAuth 为空或者 false),所以直接跳过执行 next();
(3)用户在登录页输入了用户名和密码,登录成功,保存了 token,跳转到 /home 路由;
(4)此时进入路由拦截,已经有 token了,但是还没有用户权限 permissionList,然后发请求获取用户权限列表,得到权限后 next({ path: to.path, query: to.query }); 继续往下走;
(5)再次进入路由拦截,此时有 token 和 permissionList 了,就可以根据实际业务进行跳转了。上面的代码是判断当前是不是 login 路由,如果用户登录后手动在地址栏输入 /login,则清除 token 跳转到登录页。其他的逻辑就跟具体业务相关了,就不细讲了。
4、处理用户权限
处理用户权限,在 store.js 定义一个模块 permission.js,专门用于处理用户权限相关的逻辑,用户权限列表、菜单列表都保存在此模块;
来看看 permission.js 主要做了什么:
/* permission.js */ /* 由于权限这块逻辑很多,所以在vuex添加了一个permission模块来处理权限相关的逻辑和变量 */ import httpRequest from '@/assets/js/service/http'; // http请求 import handleModule from '@/assets/js/common/handle-module'; // 处理路由、侧边栏的公共函数 import router, { dynamicRoutes } from '@/router/index'; // 默认路由配置,动态路由配置 import permissionRouter from '@/router/router'; // 需要权限的路由配置 // ... export default { // ... actions: { async FETCH_PERMISSION({ commit, state }) { // 初始化路由表,注意这里必须写,router.beforeEach 路由拦截时,多次执行 FETCH_PERMISSION commit('setPermission', []); // 发请求获取后端返回的用户权限 let data = await getUserByToken(); let userPopedoms = data.userPopedoms || []; // 保存用户的权限模块(去除掉用户管理和登录),用户管理模块可以使用,权限列表 let userPopeList = userPopedoms.filter(v => v.requestMapping !== 'user-manage' && v.requestMapping !== 'login'); commit('setUserPopedoms', userPopeList); // 根据权限筛选出我们设置好的路由并加入到 path='https://www.jb51.net/' 的children,就是home路由的children下 let routes = handleModule.getRouter(userPopedoms, permissionRouter); let homeContainer = dynamicRoutes.find(v => v.path === 'https://www.jb51.net/'); // 使用concat的目的是让 分配给用户的权限处于 children 的第0项 homeContainer.children = routes.concat(homeContainer.children); // 设置首页重定向,重定向到用户权限的第0项 homeContainer.redirect = homeContainer.children[0].name; // 根据权限生成左侧导航菜单 let sidebarMenu = handleModule.getSidebarMenu(userPopeList); commit('setMenu', sidebarMenu); // 初始路由 let initialRoutes = router.options.routes; // 动态添加路由。只有刷新页面才会清空动态添加的路由信息 router.addRoutes(dynamicRoutes); // 完整的路由表 commit('setPermission', [...initialRoutes, ...dynamicRoutes]); } }, };
(1)首先,let data = await getUserByToken(); 发请求获取用户权限,得到 data,data.userPopedoms 格式大致如下:
[ { "moduleGroupId": 1001, "moduleGroupName": "部署管理", "requestMapping": "deploy-manage", }, { "moduleGroupId": 1100, "moduleGroupName": "系统管理", "requestMapping": "sys-manage", "moduleList": [ { "moduleId": 1101, "moduleName": "系统日志", "requestMapping": "system-log", "moduleGroupId": 1100, }, { "moduleId": 1102, "moduleName": "系统告警", "requestMapping": "sys-alert", "moduleGroupId": 1100, }, ], } ]
(2)然后,根据我们写好的路由数组,进行对比,过滤得到我们要的路由。路由格式在上文“路由定义”的 router/router.js 已经提到。还要根据用户权限处理得到侧边栏菜单。
为此,我们需要两个处理函数,一个根据用户权限列表和路由数组过滤得到最终路由,另一个根据用户权限处理得到侧边栏菜单。所以另外专门创建了一个文件 handle-module.js 存放这两个函数。
/* handle-module.js */ const handleModule = { /** * 根据后台返回的权限,以及配置好的所有路由,过滤出真实路由 * @param {Array} permissionList 后台返回的用户权限列表 * @param {Array} allRouter 前端配置好的所有动态路由的集合 * @return {Array} 过滤后的路由 */ getRouter(permissionList = [], allRouter = []) { // permissions 的格式为 ["deploy-manage", "system-log"] let permissions = permissionList.reduce((acc, cur) => { if (cur.moduleList && cur.moduleList.length > 0) cur = cur.moduleList; return acc.concat(cur); }, []).map(v => v.requestMapping); return allRouter.filter(item => permissions.includes(item.meta.permitName)); }, /** * 根据后台返回的权限,生成侧边栏 * @param {Array} permissionList 后台返回的用户权限列表 * @return {Array} sidebarMenu 生成的侧边栏数组 */ getSidebarMenu(permissionList = []) { let sidebarMenu = []; permissionList.forEach(item => { let menuItem = { name: item.requestMapping, title: item.moduleGroupName, }; menuItem.children = (item.moduleList || []).map(child => ({ name: child.requestMapping, title: child.moduleName, })); sidebarMenu.push(menuItem); }); return sidebarMenu; } }; export default handleModule;
(3)上面得到过滤后的路由数组后,加入到 path 为 'https://www.jb51.net/' 的 children 下面