前端单独实现vue动态路由的示例代码
作者:你不讲 wood
Vue 动态路由权限是指在 Vue 应用程序中,根据用户的权限动态生成和控制路由的行为。这意味着不是所有的路由都在应用启动时就被硬编码到路由配置中,而是根据用户的权限信息,在运行时动态地决定哪些路由应该被加载和显示。
动态路由的优点:
安全性:
- 只有经过验证的用户才能访问其权限范围内的页面。
- 减少了由于硬编码路由导致的安全漏洞。
灵活性:
- 可以根据用户的权限动态调整应用的结构,无需重新部署整个应用即可调整路由。
- 支持按需加载(懒加载),提高应用性能。
用户体验:
- 只展示用户可以访问的菜单项,避免显示无用链接,提高用户体验。
- 用户界面更加简洁,只显示与其角色相关的功能。
可维护性:
- 简化了路由配置,因为不需要为每个角色单独编写路由配置,而是集中管理权限。
- 更容易扩展和修改权限配置,只需更新前端的权限数据即可。
开发效率:
- 开发者只需要关注业务逻辑,而不需要关心每个角色的具体路由配置。
- 减少了重复工作,提高了开发效率。
实现步骤
定义静态路由配置:
- 在项目中定义一个包含所有可能路由的静态配置文件或对象,每个路由可以附加权限信息(如角色、访问级别等)。
用户登录与鉴权:
- 用户登录时,前端存储用户的权限信息(如角色、权限列表等)。
动态生成路由:
- 根据用户的权限信息,从前端的静态路由配置中筛选出用户有权访问的路由。
- 使用递归算法或其他逻辑动态生成路由配置,并添加到 Vue Router 实例中。
动态渲染菜单:
- 根据动态生成的路由表来渲染左侧菜单或顶部导航栏,确保只显示用户有权访问的菜单项。
代码示例
配置路由器
router/index.js
// router/index.js import Vue from 'vue' import VueRouter from 'vue-router' import Layout from '@/Layout/index.vue' Vue.use(VueRouter) // export const roleMap = { // '-1':'运维管理员', // '1':'普通用户', // '2':'项目经理', // '3':'部门管理员', // '4':'综合部管理员', // '5':'部门领导' // } // 公共路由 export const routes = [ { path: '/', name: 'redirect', component: Layout, hidden: true, // 隐藏菜单 redirect: "/homePage", // 用户在地址栏输入 '/' 时会自动重定向到 /homePage 页面 }, { path: '/homePage', component: Layout, redirect: "/homePage/index", meta: { title: "首页", }, children: [ { path: 'index', name: 'homePageIndex', meta: { title: "首页", }, component: () => import('@/views/homePage/index.vue') } ] }, { path: '/login', component: () => import('@/views/login.vue'), hidden: true }, { path: '/404', component: () => import('@/views/error/404.vue'), hidden: true }, { path: '/401', component: () => import('@/views/error/401.vue'), hidden: true }, ] // 动态权限路由 export const dynamicRoutes = [ { path: '/admin', meta: { title: "系统管理", }, component: Layout, permission: ['-1', '2', '3', '4', '5'], // all 所有角色都可以访问 1 普通用户 2 项目经理 3 部门管理员 4 综合部管理员 5 部门领导 -1 项目运维管理员 children: [ { path: 'user', name: 'userIndex', meta: { title: "用户管理", }, permission: ['-1', '2', '3', '4', '5'], component: () => import('@/views/admin/user/index.vue') }, { path: 'role', name: 'roleIndex', meta: { title: "角色管理", }, permission: ['-1', '2', '3', '4', '5'], component: () => import('@/views/admin/role/index.vue'), children: [ { path: 'add', name: 'addRole', meta: { title: "添加角色", }, permission: ['-1',, '3', '4', '5'], component: () => import('@/views/admin/user/index.vue') }, { path: 'update', name: 'updateRole', meta: { title: "编辑角色", }, permission: ['-1', '2', '3', '4', '5'], component: () => import('@/views/admin/role/index.vue') } ] } ] }, { path: '/tableEcho', meta: { title: "表格管理", }, component: Layout, permission: ['-1', '1', '2'], children: [ { path: 'test', name: 'tableEchoIndex', meta: { title: "表格测试", }, permission: ['-1', '1', '2'], component: () => import('@/views/tableEcho/index.vue'), children: [ { path: 'add', name: 'addTable', hidden: true, meta: { title: "新增测试", }, permission: ['-1', '2'], component: () => import('@/views/tableEcho/add.vue') } ] }, ], }, ] const router = new VueRouter({ base: process.env.BASE_URL, routes }) export default router
上述代码定义了一个公共路由
routes
和一个动态权限控制的路由dynamicRoutes
,permission
数组定义了哪些角色拥有该路由权限, 将用户分为6个角色级别, 每个角色对应不同的角色级别,分别为
- ‘-1’:‘运维管理员’,
- ‘1’:‘普通用户’,
- ‘2’:‘项目经理’,
- ‘3’:‘部门管理员’,
- ‘4’:‘综合部管理员’,
- ‘5’:‘部门领导’,
封装路由守卫
permission.js
// permission.js import router from './router' import store from './store' import { Message } from 'element-ui' import { getStore } from '@/utils/store'; const whiteList = ['/login', '/404', '/401']; router.beforeEach((to, from, next) => { let token = getStore('token'); if (token) { /* has token*/ if (to.path === '/login') { next({ path: '/' }); } else { if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 store.dispatch('GetInfo').then((res) => { console.log('--------------', res); router.addRoutes(res) // 动态添加可访问路由表 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }).catch(err => { store.dispatch('LogOut').then(() => { Message.error(err) next(`/`) }) }) } else { next() } } } else { // 没有token if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 next() } else { next(`/login`) // 否则全部重定向到登录页 } } })
上述代码表示在路由的
beforeEach
函数里面调用 vuex 里面actions
里的方法发送接口请求获取用户信息与用户角色权限, 最后通过router.addRoutes(res)
渲染路由
permission.js 文件需引入到 main.js里面
如果项目 vue-router
版本超过 3.3.0, 需要遍历路由数组再使用 router.addRoute()
方法逐个添加路由
res.forEach( route => { router.addRoute(route); })
在 vuex 里获取用户所拥有的权限, 过滤该权限不拥有的路由
store/index.js
// store/index.js import Vue from 'vue' import Vuex from 'vuex' import { routes, dynamicRoutes } from "@/router"; import { login, getInfo, logout } from "@/api/user"; import { setStore, clearStore } from '@/utils/store'; Vue.use(Vuex) export default new Vuex.Store({ state: { routes, token: "", roleType: "", roles: [], permissions: [], sidebarRouters: [], }, getters: { token: state => state.token, roles: state => state.roles, permissions: state => state.permissions, sidebarRouters: state => state.sidebarRouters, }, mutations: { SET_TOKEN: (state, token) => { state.token = token; }, SET_USERINFO: (state, user) => { state.userInfo = user; }, SET_ROLETYPE: (state, roleType) => { state.roleType = roleType; }, SET_ROLES: (state, roles) => { state.roles = roles; }, SET_PERMISSIONS: (state, permissions) => { state.permissions = permissions; }, SET_ROUTE: (state, sidebarRouters) => { state.sidebarRouters = sidebarRouters; }, }, actions: { Login({ commit }, userInfo) { return new Promise((resolve, reject) => { login(userInfo).then(res => { setToken(res.data.token); setStore('token', res.data.token); commit('SET_TOKEN', res.data.token); resolve(); }).catch(error => { reject(error); }) }) }, // 获取用户信息 GetInfo({ commit }) { return new Promise((resolve, reject) => { getInfo().then(res => { console.log('res::: ', res); if (res.data.code === 0 || 200) { const user = res.data.sysUser; const roleType = res.data.roleType; commit('SET_USERINFO', user); // roleType 用户所用的权限级别 1 普通用户 2 项目经理 3 部门管理员 4 综合部管理员 5 部门领导 -1 项目运维管理员 setStore('ROLE_TYPE', roleType); if (res.data.roles) { // 验证返回的roles是否为真 commit('SET_ROLES', res.data.roles); commit('SET_PERMISSIONS', res.data.permissions); } else { commit('SET_ROLES', ['ROLE_DEFAULT']); } // 过滤路由 let newRouters = filterRouter(roleType, dynamicRoutes); // 连接公共路由 const sidebarRouters = routes.concat([...newRouters]) commit('SET_ROUTE', sidebarRouters); resolve(sidebarRouters); } else { reject(error); } }).catch(error => { reject(error); }) }) }, // 退出系统 LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) clearStore('token'); clearStore('userInfo') resolve() }).catch(error => { reject(error) }) }) }, }, modules: { } }) function filterRouter(roleType, routes) { return routes.filter(item => { // filter 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素,测试未通过的元素会自动剔除 // 如果一级路由的 permission 含有当前角色的 roleType if (item.permission.includes(roleType)) { // 如果该一级路由含有子路由时,递归调用该函数判断子路由是否有权限 if (Array.isArray(item.children) && item.children.length > 0) { // 递归调用该函数,最后接受校验通过后的子路由 let newChildren = filterRouter(roleType, item.children); // 如果子路由有值,则赋值给当前路由的 children,剔除校验不通过的子路由 if (newChildren.length > 0) { item.children = newChildren; } else if (newChildren.length === 0) { // 如果子路由为空,则删除该路由的 children 属性 delete item.children; } } // 最后返回 true, 表示该路由通过权限校验 return true; } }) }
上述代码通过
getInfo
接口获取用户权限, 通过函数filterRouter
过滤掉该角色不拥有的路由, 通过concat
方法合并routes
公共路由, 最后通过resolve
返回
文件布局如下
下图为页面渲染的菜单(项目经理角色)
左侧菜单实现参考链接: Elemnt-UI + 递归组件实现后台管理系统左侧菜单
前端结合后端接口请求实现动态路由参考连接: 前端 + 接口请求实现 vue 动态路由
总结
在用户登录成功后从服务器获取用户的权限信息,在 vuex 的异步处理函数中过滤掉角色权限不存在的路由,使用 concat()
合并公共路由,最后使用 router.addRoutes(res)
动态添加可访问的路由。这样可以确保应用根据用户的权限动态加载相应的路由,增强安全性与灵活性。