修复动态导入组件问题
修复刷新 404 的 bug
优化流程,简化逻辑,去除_import.js 文件,改为直接动态导入
优化 404 页面
更新为最新的 vue-element-template 的模板
增加思路说明
优化部分代码
增加 QQ 交流群,有问题请加群
在今年年初在掘金发布了一篇文章记一次 Vue 动态渲染路由的实现,现在代码经过不断的 Review
现在完全优化了之前的实现方法,代码量减少很多,逻辑更加简单,同时也更加稳定
demo 已经部署到 github,欢迎体验~~ vue-element-asyncLogin, 你的 start 是我的动力!
前端路由鉴权相信只要了解过vue-element-admin的都知道,前端鉴权方案是完全可行的,思路清晰,难度适中,项目中完全可以使用,那么相对来说动态路由优势在什么地方呢
- 前端鉴权不够灵活,线上版本每次修改权限页面,都需要重新打包项目
- 中小型项目中
前端鉴权明显更加好用,成本更低,程序员们也不用 996 了(雾),但是对于权限等级很多,并且比较大的项目,维护这一套鉴权路由,毫无疑问是一个大工程,并且面对频繁变更的需求,bug 会出现的更加频繁,前端工程师工作量大大增加,这时候似乎前端鉴权就不再是好的方案 - 动态路由并不是回归到刀耕火种的时代,而是一种新的思路,路由配置还是由前端完成,仅仅将状态交给了后端,不同角色的路由显示交给后端控制,前端不需要管理路由,最多只需要管理权限颗粒化的问题
- 路由跳转 先判断是否登录 未登录只能访问白名单页面,访问其他页面全部重定向到登录页面
- 登录行为触发,获取动态路由,递归解析动态路由信息,并且 addRouter,同时存储到 Vuex,并且记录获取路由的状态
- 跳转页面不会获取动态路由,刷新页面重新获取动态路由
相比较之前使用 localStorage 存储登录状态,现在把登录状态交给 cookice 进行管理
路由信息全部交给 Vuex 进行管理,不再从 localStorage 里面走,增加了系统的稳定性
配置基础路由
具体的实现思路
router/router.js
// ...... // 静态路由 export const StaticRouterMap = [ { path: "/login", component: login, meta: { title: "管理员登录" }, hidden: true }, { path: "/user", component: userLogin, redirect: "/user/userlogin", name: "user", hidden: true, children: [ { path: "userLogin", component: () => import("@/views/userLogin/components/login"), meta: { title: "商户登录" } }, { path: "userRegistry", component: () => import("@/views/userLogin/components/registry"), meta: { title: "商户注册" } } ] }, { path: "/", component: Layout, redirect: "/dashboard", name: "dashboard", children: [ { path: "dashboard", component: () => import("@/views/dashboard/index"), meta: { title: "根目录", icon: "dashboard", affix: true } } ] }, { path: "/404", component: () => import("@/views/404"), hidden: true } ]; export default new Router({ mode: "history", scrollBehavior: () => ({ y: 0 }), routes: StaticRouterMap });与后端同学定制路由结构 (以下为 json)
后端会根据当前用户权限动态返回路由结构 前端不再需要考虑权限问题
[ { "id": 1, "name": "Example", "code": null, "description": null, "url": "/example", "component": "layout", "generatemenu": 1, "sort": 0, "parentId": null, "permName": null, "redirect": "/example/table", "title": "普通用户", "icon": "example", "children": [ { "id": 2, "name": "Table", "code": null, "description": null, "url": "table", "component": "table", "generatemenu": 1, "sort": 0, "parentId": 1, "permName": null, "redirect": "", "title": "Table", "icon": "table", "children": null }, { "id": 3, "name": "Tree", "code": null, "description": null, "url": "tree", "component": "tree", "generatemenu": 1, "sort": 0, "parentId": 1, "permName": null, "redirect": "", "title": "Tree", "icon": "tree", "children": null } ] } ]处理后端原始路由数据
../utils/addRouter
递归写入比之前版本的递归删除更加稳定,代码量也更少
注意,路由结构与目录结构是对应的
/** * 生成路由 * @param {Array} routerlist 格式化路由 * @returns */ export function addRouter(routerlist) { const router = []; try { routerlist.forEach(e => { let e_new = { path: e.url, name: e.name, component: () => e.component === "layout" ? import("@/layout") : import(`@/views/${e.component}/index`) }; if (e.children) { const children = addRouter(e.children); // 保存权限 e_new = { ...e_new, children: children }; } if (e.redirect) { e_new = { ...e_new, redirect: e.redirect }; } if (e.generatemenu === 0) { e_new = { ...e_new, hidden: true }; } if (e.icon !== "" && e.title !== "") { e_new = { ...e_new, meta: { title: e.title, icon: e.icon } }; } else if (e.title !== "" && e.icon === "") { e_new = { ...e_new, meta: { title: e.title } }; } router.push(e_new); }); } catch (error) { console.error(error); return []; } return router; }处理后的路由
我们处理后的路由后面需要与现有的 router 进行拼接,这里需要根据需求 修改处理路由的规则
以上的都是准备工作,就是为了将初始路由与后端返回的动态路由进行拼接
这部分代码也是优化的核心
import router from "./router"; import store from "./store"; import user from "./store/modules/user"; import { getToken, removeToken } from "./utils/auth"; import NProgress from "nprogress"; // Progress 进度条 import "nprogress/nprogress.css"; // Progress 进度条样式 import { Message } from "element-ui"; import { getRouter } from "./api/login"; import { addRouter } from "./utils/addRouter"; const whiteList = ["/login"]; router.beforeEach((to, from, next) => { NProgress.start(); if (getToken()) { // 判断cookice是否存在 不存在即为未登录 if (to.path !== "/login") { if (user.state.init) { // 获取了动态路由 data一定true,就无需再次请求 直接放行 next(); } else { // data为false,一定没有获取动态路由,就跳转到获取动态路由的方法 gotoRouter(to, next); } } else { Message({ message: "您已经登录", type: "info" }); next("/"); } } else { if (whiteList.indexOf(to.path) !== -1) { // 免登陆白名单 直接进入 next(); } else { if (to.path !== "/login") { // 重定向到登录页面 不能这么写 因为假如之前的角色是 管理员页面 后又登陆了非管理员 重定向的页面就可能不存在,就会导致404 // next(`/login?redirect=${to.path}`) next("/login"); } else { next(); } } } }); router.afterEach((to, from) => { NProgress.done(); // 结束Progress }); function gotoRouter(to, next) { getRouter(store.getters.token) // 获取动态路由的方法 .then(res => { console.log("解析后端动态路由", res); const asyncRouter = addRouter(res.data.router); // 进行递归解析 store.dispatch("user/setroles", res.data.permit); return asyncRouter; }) .then(asyncRouter => { router.addRoutes(asyncRouter); // vue-router提供的addRouter方法进行路由拼接 console.log(asyncRouter); store.dispatch("user/setRouterList", asyncRouter); // 存储到vuex store.dispatch("user/GetInfo"); store.commit("user/set_init", true); next({ ...to, replace: true }); // hack方法 确保addRoutes已完成 }) .catch(e => { console.log(e); removeToken(); }); }Vuex 内部的逻辑
import { StaticRouterMap } from '../../router/index' state: { //..... RouterList: [] // 动态路由 }, mutations: { set_router: (state, RouterList) => { state.RouterList = RouterList } }, action: { // 动态设置路由 此为设置设置途径 setRouterList({ commit }, routerList) { commit('set_router', StaticRouterMap.concat(routerList)) // 进行路由拼接并存储 }, }相对之前的逻辑要简单很多
需要注意的是 通过 addRoutes 合并的路由 不会被this.$router.options.routes获取到,所以需要将获取的路由拼接到this.$router.options.routes上
最后修改渲染侧边栏部分部分的代码
src\views\layout\components\Sidebar\index.vue
computed: { // .... routes() { return this.$store.getters.routerList }, // .... }我已精心准备了一个简单的 demo vue-element-asyncLogin,欢迎体验,如果对你有帮助,请不要吝啬你的 start~~1

