vue权限路由实现方式总结二

之前已经写过一篇关于vue权限路由实现方式总结的文章,经过一段时间的踩坑和总结,下面说说目前我认为比较“完美”的一种方案:菜单与路由完全由后端提供。 菜单与路由完全由后端返回 这种方案前文也有提过,现在更加具体的说一说。 很多人喜欢把路由处理成菜单,或者把菜单处理成路由(我之前也是这样做的),最后发现挖的坑越来越深。 应用的菜单可能是两级,可能是三级,甚至是四到五级,而路由一般最多不会超过三级。如果应用的菜单达到五级,而用两级路由就可以就解决的情况下,为了能根据路由生成相应的菜单,有的人会弄出个五级路由出来。。。 所以墙裂建议,菜单数据与路由数据独立开,只要能根据菜单跳转到相应的路由即可。 菜单与路由都由后端提供,就需要就菜单与路由做相应的的维护功能。菜单上一些属性也是必须的,比如标题、跳转路径(也可以用跳转名称,对应路由名称即可,因为vue路由能根据名称进行跳转)。路由数据维护vue路由所需字段即可。 当然,做权限控制还得在菜单和路由上都维护相应的权限码,后端根据用户的权限过滤出用户能访问的菜单与路由。 下面是一份由后端返回的菜单和路由例子 let permissionMenu = [ { title: "系统", path: "/system", icon: "folder-o", children: [ { title: "系统设置", icon: "folder-o", children: [ { title: "菜单管理", path: "/system/menu", icon: "folder-o" }, { title: "路由管理", path: "/system/route", icon: "folder-o" } ] }, { title: "权限管理", icon: "folder-o", children: [ { title: "功能管理", path: "/system/function", icon: "folder-o" }, { title: "角色管理", path: "/system/role", icon: "folder-o" }, { title: "角色权限管理", path: "/system/rolepermission", icon: "folder-o" }, { title: "角色用户管理", path: "/system/roleuser", icon: "folder-o" }, { title: "用户角色管理", path: "/system/userrole", icon: "folder-o" } ] }, { title: "组织架构", icon: "folder-o", children: [ { title: "部门管理", path: "", icon: "folder-o" }, { title: "职位管理", path: "", icon: "folder-o" } ] }, { title: "用户管理", icon: "folder-o", children: [ { title: "用户管理", path: "/system/user", icon: "folder-o" } ] } ] } ] let permissionRouter = [ { name: "系统设置", path: "/system", component: "layoutHeaderAside", componentPath:'layout/header-aside/layout', meta: { title: '系统设置' }, children: [ { name: "菜单管理", path: "/system/menu", meta: { title: '菜单管理' }, component: "menu", componentPath:'pages/sys/menu/index', }, { name: "路由管理", path: "/system/route", meta: { title: '路由管理' }, component: "route", componentPath:'pages/sys/menu/index', } ] }, { name: "权限管理", path: "/system", component: "layoutHeaderAside", componentPath:'layout/header-aside/layout', meta: { title: '权限管理' }, children: [ { name: "功能管理", path: "/system/function", meta: { title: '功能管理' }, component: "function", componentPath:'pages/sys/menu/index', }, { name: "角色管理", path: "/system/role", meta: { title: '角色管理' }, component: "role", componentPath:'pages/sys/menu/index', }, { name: "角色权限管理", path: "/system/rolepermission", meta: { title: '角色权限管理' }, component: "rolePermission", componentPath:'pages/sys/menu/index', }, { name: "角色用户权限管理", path: "/system/roleuser", meta: { title: '角色用户管理' }, component: "roleUser", componentPath:'pages/sys/menu/index', }, { name: "用户角色权限管理", path: "/system/userrole", meta: { title: '用户角色管理' }, component: "userRole", componentPath:'pages/sys/menu/index', } ] }, { name: "用户管理", path: "/system", component: "layoutHeaderAside", componentPath:'layout/header-aside/layout', meta: { title: '用户管理' }, children: [ { name: "用户管理", path: "/system/user", meta: { title: '用户管理' }, component: "user", componentPath:'pages/sys/menu/index', } ] } ] 可以看到菜单最多达到三级,路由只有两级,通过菜单上的path与路由的path相对应,当点击菜单的时候就能正确的跳转。 有个小技巧:在路由的meta上维护一个title属性,在页面切换的时候,如果需要动态改变浏览器标签页的标题,可以直接从当前路由上取到,不需要到菜单上取。 菜单数据可以作为左侧菜单的数据源,也可以是顶部菜单的数据源。有的系统内容比较多,顶部可能是系统模块,左侧是模块下的菜单,切换顶部不同模块,左侧菜单要动态进行切换。做类似功能的时候,因为菜单数据与路由分开,只要关注与菜单即可,比如在菜单上加上模块属性。 当前的路由数据是完全符合vue路由声明规则的,但是直接使用添加路由的方法addRoutes动态添加路由是不行的。因为vue路由的component属性必须是一个组件,比如 { name: "login", path: "/login", component: () => import("@/pages/Login.vue") } 而目前我们得到的路由数据中component属性是一个字符串。需要根据这个字符串将component属性处理成真正的组件。在路由数据中除了component这个属性不符合vue路由要求,还多了componentPath这个属性。下面介绍两种分别根据这两个属性处理路由的方法。 处理路由 使用routerMapComponents 这个名称是我取的,其实就是维护一个js文件,将组件按照key-value的规则导出,比如: import layoutHeaderAside from '@/layout/header-aside' export default { "layoutHeaderAside": layoutHeaderAside, "menu": () => import(/* webpackChunkName: "menu" */'@/pages/sys/menu'), "route": () => import(/* webpackChunkName: "route" */'@/pages/sys/route'), "function": () => import(/* webpackChunkName: "function" */'@/pages/permission/function'), "role": () => import(/* webpackChunkName: "role" */'@/pages/permission/role'), "rolePermission": () => import(/* webpackChunkName: "rolepermission" */'@/pages/permission/rolePermission'), "roleUser": () => import(/* webpackChunkName: "roleuser" */'@/pages/permission/roleUser'), "userRole": () => import(/* webpackChunkName: "userrole" */'@/pages/permission/userRole'), "user": () => import(/* webpackChunkName: "user" */'@/pages/permission/user') } 这里的key就是与后端返回的路由数据的component属性对应。所以拿到后端返回的路由数据后,使用这份规则将路由数据处理一下即可: const formatRoutes = function (routes) { routes.forEach(route => { route.component = routerMapComponents[route.component] if (route.children) { formatRoutes(route.children) } }) } formatRoutes(permissionRouter) router.addRoutes(permissionRouter); 而且,规则列表里维护的组件都会被webpack打包成单独的js文件,即使处理路由数据的时候没有被使用到(没有被routerMapComponents[route.component]匹配出来)。当我们需要给一个页面做多种布局的时候,只需要在菜单维护界面上将component修改为routerMapComponents中相应的key即可。 标准的异步组件 按照vue官方文档的异步组件的写法,得到两种处理路由的方法,并且用到了路由数据中的componentPath: 第一种写法: const formatRoutesByComponentPath = function (routes) { routes.forEach(route => { route.component = function (resolve) { require([`../${route.componentPath}.vue`], resolve) } if (route.children) { formatRoutesByComponentPath(route.children) } }) } formatRoutesByComponentPath(permissionRouter); router.addRoutes(permissionRouter); 第二种写法: const formatRoutesByComponentPath = function (routes) { routes.forEach(route => { route.component = () => import(`../${route.componentPath}.vue`) if (route.children) { formatRoutesByComponentPath(route.children) } }) } formatRoutesByComponentPath(permissionRouter); router.addRoutes(permissionRouter); 其实在大多数人的认知里(包括我),这样的代码webpack应该是处理不了的,毕竟componentPath是运行时才确定,而webpack是“编译”时进行静态处理的。 为了验证这样的代码能不能正常运行,写了个简单的demo,感兴趣的可以下载到本地运行。 测试的结果是:上面的两种写法程序都可以正常运行。 观察打包后的代码,发现所有的组件都被打包,不管是否被使用(之前routerMapComponents方式中,只有维护进列表中的组件才会打包)。 所有的组件都被打包了,但是两种方法打包后的代码却是天差地别。 使用 route.component = function (resolve) { require([`../${route.componentPath}.vue`], resolve) } 处理路由,打包后 0开头的文件是page404.vue打包后的代码,1开头的是home.vue的。这两个组件能分别打包,是因为main.js中显式的使用的这两个组件: ... let routers = [ { name: "home", path: "/", component: () => import(/* webpackChunkName: "home" */"@/pages/home.vue") }, { name: "404", path: "*", component: () => import(/* webpackChunkName: "page404" */"@/pages/page404.vue") } ]; let router = new Router({ // mode: 'history', // require service support scrollBehavior: () => ({ y: 0 }), routes: routers }); ... 而4开头的文件就是其它全部组件打包后的,而且额外带了点东西: webpackJsonp([4, 0], { "/EbY": function(e, t, n) { var r = { "./App.vue": "M93x", "./pages/dynamic.vue": "fJxZ", "./pages/home.vue": "vkyI", "./pages/nouse.vue": "HYpT", "./pages/page404.vue": "GVrJ" }; function i(e) { return n(a(e)) } function a(e) { var t = r[e]; if (! (t + 1)) throw new Error("Cannot find module '" + e + "'."); return t } i.keys = function() { return Object.keys(r) }, i.resolve = a, e.exports = i, i.id = "/EbY" }, GVrJ: function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = { render: function() { var e = this.$createElement, t = this._self._c || e; return t("div", [this._v("\n 404\n "), t("div", [t("router-link", { attrs: { to: "/" } }, [this._v("返回首页")])], 1)]) }, staticRenderFns: [] }; var i = n("VU/8")({ name: "page404" }, r, !1, function(e) { n("tqPO") }, "data-v-5b14313a", null); t. default = i.exports }, HYpT: function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = { render: function() { var e = this.$createElement; return (this._self._c || e)("div", [this._v("\n 从未使用的组件\n")]) }, staticRenderFns: [] }; var i = n("VU/8")({ name: "nouse" }, r, !1, function(e) { n("v4yi") }, "data-v-d4fde316", null); t. default = i.exports }, WMa5: function(e, t) {}, fJxZ: function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = { render: function() { var e = this.$createElement, t = this._self._c || e; return t("div", [t("div", [this._v("动态路由页")]), this._v(" "), t("router-link", { attrs: { to: "/" } }, [this._v("首页")])], 1) }, staticRenderFns: [] }; var i = n("VU/8")({ name: "dynamic" }, r, !1, function(e) { n("WMa5") }, "data-v-71726d06", null); t. default = i.exports }, tqPO: function(e, t) {}, v4yi: function(e, t) {} }); dynamic.vue,nouse.vue都被打包进去了,而且page404.vue又被打包了一次(???)。 而且有点东西: var r = { "./App.vue": "M93x", "./pages/dynamic.vue": "fJxZ", "./pages/home.vue": "vkyI", "./pages/nouse.vue": "HYpT", "./pages/page404.vue": "GVrJ" }; 这应该就是运行时使用componentPath处理路由,程序也能正常运行的关键点。 为了弄清楚page404.vue为什么又被打包了一次,我加了个simple.vue,而且在main.js也显式的import进去了,打包后发现simple.vue也是单独打包的,唯独page404.vue被打包了两次。暂时无解。。。 使用 route.component = () => import(`../${route.componentPath}.vue`) 处理路由,打包后 0开头的文件是page404.vue打包后的代码,1开头的是home.vue的,4开头是nouse.vue的,5开头是dynamic.vue的。 所有的组件都被单独打包了,而且home.vue打包后的代码还多了写东西: webpackJsonp([1], { "rF/f": function(e, t) {}, sTBc: function(e, t, n) { var r = { "./App.vue": ["M93x"], "./pages/dynamic.vue": ["fJxZ", 5], "./pages/home.vue": ["vkyI"], "./pages/nouse.vue": ["HYpT", 4], "./pages/page404.vue": ["GVrJ", 0] }; function i(e) { var t = r[e]; return t ? Promise.all(t.slice(
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信