注意:vue-router是无法完全控制前端路由权限。
1、实现思路
使用vue-router实例函数addRoutes动态添加路由规则,不多废话直接上思维导图:
2、实现步骤
2.1、路由匹配判断
// src/router.js import Vue from 'vue'; import Store from '@/store'; import Router from 'vue-router'; import Cookie from 'js-cookie'; const routers = new Router({ base : "/test", // 定义默认路由比如登录、404、401等 routes : [{ path : "/404", // ... },{ path : "/401", // ... }] }) // ...省略部分代码 routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 if(isMatched){ }else{ } })
通过vue-router前置守卫beforeEach中参数to来简单的实现匹配结果
2.2、登录访问控制
在实际开发中路由常常存在是否登录访问和是否需要登录访问的情况,于是可以通过token和路由配置meta信息中定义isAuth字段来区分。
// ...省略部分重复代码 const openRouters = []; const authRouters = [{ path : "order/list", // ... meta : { // 是否身份验证(至于默认定义false还是true由开发者自定义) isAuth : true } }]; routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 let isLogin = Cookie.get("token") || null; let { isAuth } = (meta || {}); if(isMatched){ // 匹配到路由 if(isAuth){ // 需要登录访问 if(isLogin){ // 已登录访问 next(); // 调用钩子函数 }else{ // 未登录访问 next("/login"); // 跳转登录 } }else{ // 不需要登录访问 next(); // 调用钩子函数 } }else{ // 未匹配到路由 if(isLogin){ // 已登录访问 }else{ // 未登录访问 next("/login"); // 跳转登录 } } })
2.3、动态添加路由规则
实现动态添加路由规则只需要使用vue-router实例方法router.addRoutes(routes: Array) 。
那么问题来了,我们怎么才能获取到需要动态添加的路由规则呢?
2.4、构建路由规则匹配函数
假如后台获取到的路由权限列表是这样的:
[{ resourceUrl : "/order/list", childMenu : ... }]
为了对比用户权限和路由是否匹配我们需要提取出权限路由数组
// 简单的通过递归获取到了所有权限url export function getAuthRouters(authMenu) { let authRouters = []; (authMenu || []).forEach((item) => { const { resourceUrl, childMenu } = item; resourceUrl && authRouters.push(resourceUrl); if (childMenu && childMenu.length > 0) { // 合并子级菜单 authRouters = [...authRouters, ...getAuthRouters(childMenu)]; } }); return authRouters; }
通过getAuthRouters函数获取到了所有用户路由权限,接下来是要怎么和vue-router路由匹配呢?
这要和(我这里使用的是RBAC模型)系统配置权限关联上。vue-router路由规则要和权限配置保持一致。所以通过递归动态拼接vue-router路由规则和用户拥有的路由权限做对比。如果匹配就保留该路由;然后得到一份过滤后的vue-router路由规则配置。最后通过实例方法addRoutes添加路由规则。具体实现代码如下:
// src/utils/index.js const { pathToRegexp } = require('path-to-regexp'); export function createAuthRouters(authRouters) { const isAuthUrl = (url) => { return (authRouters || []).some((cUrl) => { return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString(); }); }; return function createRouters(routers, upperPath) { let nRouters = []; (routers || []).forEach((item) => { const { children, path, name } = item; let isMatched = false, nItem = { ...item }, fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'), nChildren = null; children && (nChildren = createRouters(children, fullPath)); // 1.当前路由匹配 if (isAuthUrl(fullPath)) { isMatched = true; } // 2.存在子路由匹配 if (nChildren && nChildren.length > 0) { nItem.children = nChildren; isMatched = true; } // 特殊处理(不需要可以删除) if(name === "home"){ isMatched = true; } // nItem isMatched && nRouters.push(nItem); }); return nRouters; }; }
值得注意的是createAuthRouters方法通过变量isMatched控制是否保留,之所以通过变量来决定是因为嵌套路由中父路由可能无法匹配,但是子路由能匹配所以父路由规则也需要子路参与是否保留。比如:
// 路由规则 const routers = new Router({ base : "/test", // 定义默认路由比如登录、404、401等 routes : [{ path : "/", ... children : [{ path : "login", ... },{ path : "about", ... },{ path : "order", ... children : [{ path : "id" }] }] }] }) // 用户权限 ["/order/id"]; // 在匹配的过程中 "/" 不等于 "/order/id" 、"/" 不等于 "/order" 但是子路由 "/order/id" == "/order/id" 所以不但要保留 path : "/",还得保留 path : "order" 嵌套层。
2.5、动态注册
// ...省略部分重复代码 const openRouters = []; const authRouters = [{ path : "order/list", // ... meta : { // 是否身份验证(至于默认定义false还是true由开发者自定义) isAuth : true } }]; /* 动态注册路由 */ async function AddRoutes() { // 获取用户路由权限 let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU); try { const { code, data } = res || {}; if (code === '000') { let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base); // 注册路由 routes.addRoutes([].concat(newAuthRoutes, openRouters)); // 设置已注册 Store.commit('UPDATE_IS_ADD_ROUTERS', true); // 保存菜单信息 Store.commit('UPDATE_MENU_INFO', data); } } catch (error) { console.error('> AddRoutes() - error:', error); } } routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 let isLogin = Cookie.get("token") || null; let { isAuth } = (meta || {}); if(isMatched){ // 匹配到路由 if(isAuth){ // 需要登录访问 if(isLogin){ // 已登录访问 next(); // 调用钩子函数 }else{ // 未登录访问 next("/login"); // 跳转登录 } }else{ // 不需要登录访问 next(); // 调用钩子函数 } }else{ // 未匹配到路由 if(isLogin){ // 已登录访问 AddRoutes(); next(); }else{ // 未登录访问 next("/login"); // 跳转登录 } } })
2.6、归类整理
/* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配 let isAuth = (meta || {}).isAuth; // 是否授权访问 let { isAddRoutes } = Store.state; // 注册路由 let isLogin = Cookie.get('token') || null; // 是否登录 if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) { // next() // 1.匹配路由 && 未登录访问 // 2.匹配路由 && 登录访问 && 登录 next(); } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) { // 登录 // 1.匹配路由 && 登录访问 && 未登录 // 2.未匹配路由 && 未登录 next(`/login"color: #ff0000">3、完整实现代码
// src/utils/index.js const { pathToRegexp } = require('path-to-regexp'); export function getAuthRouters(authMenu) { let authRouters = []; (authMenu || []).forEach((item) => { const { resourceUrl, childMenu } = item; resourceUrl && authRouters.push(resourceUrl); if (childMenu && childMenu.length > 0) { // 合并子级菜单 authRouters = [...authRouters, ...getAuthRouters(childMenu)]; } }); return authRouters; } /** * * @param { Array } authRouters */ export function createAuthRouters(authRouters) { const isAuthUrl = (url) => { return (authRouters || []).some((cUrl) => { return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString(); }); }; return function createRouters(routers, upperPath) { let nRouters = []; (routers || []).forEach((item) => { const { children, path, name } = item; let isMatched = false, nItem = { ...item }, fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'), nChildren = null; children && (nChildren = createRouters(children, fullPath)); // 1.当前路由匹配 if (isAuthUrl(fullPath)) { isMatched = true; } // 2.存在子路由匹配 if (nChildren && nChildren.length > 0) { nItem.children = nChildren; isMatched = true; } // 特殊处理 if(name === "home"){ isMatched = true; } // nItem isMatched && nRouters.push(nItem); }); return nRouters; }; } // src/router.js import Vue from 'vue'; import Store from '@/store'; import Router from 'vue-router'; import Cookie from 'js-cookie'; const openRouters = []; const authRouters = [{ path : "order/list", // ... meta : { // 是否身份验证(至于默认定义false还是true由开发者自定义) isAuth : true } }]; /* 动态注册路由 */ async function AddRoutes() { // 获取用户路由权限 let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU); try { const { code, data } = res || {}; if (code === '000') { let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base); // 注册路由 routes.addRoutes([].concat(newAuthRoutes, openRouters)); // 设置已注册 Store.commit('UPDATE_IS_ADD_ROUTERS', true); // 保存菜单信息 Store.commit('UPDATE_MENU_INFO', data); } } catch (error) { console.error('> AddRoutes() - error:', error); } } /* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配 let isAuth = (meta || {}).isAuth; // 是否授权访问 let { isAddRoutes } = Store.state; // 注册路由 let isLogin = Cookie.get('token') || null; // 是否登录 if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) { // next() // 1.匹配路由 && 未登录访问 // 2.匹配路由 && 登录访问 && 登录 next(); } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) { // 登录 // 1.匹配路由 && 登录访问 && 未登录 // 2.未匹配路由 && 未登录 next(`/login?r=${origin}/e-lottery${path}`); } else if (!isMatched && isLogin && isAddRoutes) { // 404 // 1.未匹配路由 && 登录 && 动态注册路由 next('/404'); } else if (!isMatched && isLogin && !isAddRoutes) { // 注册路由 // 1.未匹配路由 && 登录 && 未动态注册路由 AddRoutes(); next(); } });虽然前端能够通过vue-router实现对路由权限的控制,但是实际是伪权限控制,无法达到完全控制;强烈建议对于需要控制路由权限的系统采用后端控制。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]