前端 + 接口哀求实现 vue 动态路由

[复制链接]
发表于 2026-1-24 06:00:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
前端 + 接口哀求实现 vue 动态路由

在 Vue 应用中,通过前端团结后端接口哀求来实现动态路由是一种常见且有用的权限控制方案。这种方法答应前端根据用户的脚色和权限,动态天生和加载路由,而不是在应用启动时就固定全部的路由设置。
实现原理


  • 界说静态路由设置:

    • 在项目标初始阶段,界说一套完备的路由设置,这些设置包罗了全部大概的路由路径和相干的权限信息。

  • 用户登录与鉴权:

    • 用户登录时,前端向后端发送哀求验证用户的身份。
    • 服务器验证乐成后,返回一个包罗用户信息和权限的数据对象。

  • 获取用户权限信息:

    • 前端根据登录时得到的令牌(如 JWT),再次向后端哀求获取当前用户的权限信息。
    • 权限信息大概包罗用户的脚色、可以或许访问的资源等。

  • 动态天生路由:

    • 前端根据从后端获取的权限信息,动态天生符适用户权限的路由表。
    • 这个过程可以通过递归算法处理处罚路由设置树,根据用户的权限过滤掉无权访问的路由。

  • 动态添加路由:

    • 使用 Vue Router 的 router.addRoutes(routes) 方法将天生的路由动态添加到路由实例中。
    • 如许只有颠末权限验证的路由才会被添加,从而实现了权限控制。

  • 动态渲染菜单:

    • 左侧菜单通常是基于天生的路由表来渲染的,因此只有用户有权访问的路由才会在菜单中体现。

优点


  • 安全性:

    • 只有颠末验证的用户才气访问其权限范围内的页面。
    • 淘汰了由于硬编码路由导致的安全毛病。

  • 机动性:

    • 可以根据用户的权限动态调解应用的布局,无需重新摆设整个应用即可调解路由。
    • 支持按需加载(懒加载),进步应用性能。

  • 用户体验:

    • 只展示用户可以访问的菜单项,制止体现无用链接,进步用户体验。
    • 用户界面更加轻便,只体现与其脚色相干的功能。

  • 可维护性:

    • 简化了路由设置,由于不须要为每个脚色单独编写路由设置,而是会合管理权限。
    • 更轻易扩展和修改权限设置,只需更新后端的权限数据即可。

  • 开发服从:

    • 开发者只须要关注业务逻辑,而不须要关心每个脚色的详细路由设置。
    • 淘汰了重复工作,进步了开发服从。

示例

在前导航路由钩子 beforeEach 函数里发送接口哀求获取路由信息
permission.js
  1. // permission.js
  2. import router from './router'
  3. import store from './store'
  4. import { Message } from 'element-ui'
  5. import { getStore } from '@/utils/store';
  6. const whiteList = ['/login', '/404', '/401'];
  7. router.beforeEach((to, from, next) => {
  8.   let token = getStore('token');
  9.   if (token) {
  10.     /* has token*/
  11.     if (to.path === '/login') {
  12.       next({ path: '/' });
  13.     } else {
  14.       if (store.getters.roles.length === 0) {
  15.         // 判断当前用户是否已拉取完user_info信息
  16.         store.dispatch('GetInfo').then(() => {
  17.           // 获取路由信息
  18.           store.dispatch('GenerateRoutes').then((res) => {
  19.             console.log('--------------', res);
  20.             // 根据roles权限生成可访问的路由表
  21.             router.addRoutes(res) // 动态添加可访问路由表
  22.             next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
  23.           })
  24.         }).catch(err => {
  25.           store.dispatch('LogOut').then(() => {
  26.             Message.error(err)
  27.             next(`/`)
  28.           })
  29.         })
  30.       } else {
  31.         next()
  32.       }
  33.     }
  34.   } else {
  35.     // 没有token
  36.     if (whiteList.indexOf(to.path) !== -1) {
  37.       // 在免登录白名单,直接进入
  38.       next()
  39.     } else {
  40.       next(`/login`) // 否则全部重定向到登录页
  41.     }
  42.   }
  43. })
复制代码
  permission.js 文件需引入到 main.js 里
  假如项目 vue-router 版本高出 3.3.0, 须要遍历路由数组再使用 router.addRoute() 方法逐个添加路由
  1. res.forEach( route => {
  2.         router.addRoute(route);
  3. })
复制代码
假设后端接口返回的路由权限如下
  1. [
  2.   {
  3.     path: '/admin',
  4.     meta: {
  5.       title: "系统管理",
  6.     },
  7.     component: 'Layout',
  8.     children: [
  9.       {
  10.         path: 'user',
  11.         name: 'userIndex',
  12.         meta: {
  13.           title: "用户管理",
  14.         },
  15.         component: '/admin/user/index.vue'
  16.       },
  17.       {
  18.         path: 'role',
  19.         name: 'roleIndex',
  20.         meta: {
  21.           title: "角色管理",
  22.         },
  23.         component: '/admin/role/index.vue',
  24.         children: [
  25.           {
  26.             path: 'add',
  27.             name: 'addRole',
  28.             meta: {
  29.               title: "添加角色",
  30.             },
  31.             component: '/admin/user/index.vue'
  32.           },
  33.           {
  34.             path: 'update',
  35.             name: 'updateRole',
  36.             meta: {
  37.               title: "编辑角色",
  38.             },
  39.             component: '/admin/role/index.vue'
  40.           }
  41.         ]
  42.       }
  43.     ]
  44.   },
  45.   {
  46.     path: '/tableEcho',
  47.     meta: {
  48.       title: "表格管理",
  49.     },
  50.     component: 'Layout',
  51.     children: [
  52.       {
  53.         path: 'test',
  54.         name: 'tableEchoIndex',
  55.         meta: {
  56.           title: "表格测试",
  57.         },
  58.         component: '/tableEcho/index.vue',
  59.         children: [
  60.           {
  61.             path: 'add',
  62.             name: 'addTable',
  63.             hidden: true,
  64.             meta: {
  65.               title: "新增测试",
  66.             },
  67.             component: '/tableEcho/add.vue'
  68.           }
  69.         ]
  70.       },
  71.     ],
  72.   },
  73. ]
复制代码
vuex 处理处罚数据
store/index.vue
  1. // store/index.vue
  2. import Vue from 'vue'
  3. import Vuex from 'vuex'
  4. import { routes, dynamicRoutes } from "@/router";
  5. import { login, getInfo, logout, getRouters } from "@/api/user";
  6. import { setStore, clearStore } from '@/utils/store';
  7. import Layout from '@/Layout/index.vue'
  8. Vue.use(Vuex)
  9. export default new Vuex.Store({
  10.   state: {
  11.     routes,
  12.     token: "",
  13.     roleType: "",
  14.     roles: [],
  15.     permissions: [],
  16.     sidebarRouters: [],
  17.   },
  18.   getters: {
  19.     token: state => state.token,
  20.     roles: state => state.roles,
  21.     permissions: state => state.permissions,
  22.     sidebarRouters: state => state.sidebarRouters,
  23.   },
  24.   mutations: {
  25.     SET_TOKEN: (state, token) => {
  26.       state.token = token;
  27.     },
  28.     SET_USERINFO: (state, user) => {
  29.       state.userInfo = user;
  30.     },
  31.     SET_ROLETYPE: (state, roleType) => {
  32.       state.roleType = roleType;
  33.     },
  34.     SET_ROLES: (state, roles) => {
  35.       state.roles = roles;
  36.     },
  37.     SET_PERMISSIONS: (state, permissions) => {
  38.       state.permissions = permissions;
  39.     },
  40.     SET_ROUTE: (state, sidebarRouters) => {
  41.       state.sidebarRouters = sidebarRouters;
  42.     },
  43.   },
  44.   actions: {
  45.     Login({ commit }, userInfo) {
  46.       return new Promise((resolve, reject) => {
  47.         login(userInfo).then(res => {
  48.           setToken(res.data.token);
  49.           setStore('token', res.data.token);
  50.           commit('SET_TOKEN', res.data.token);
  51.           resolve();
  52.         }).catch(error => {
  53.           reject(error);
  54.         })
  55.       })
  56.     },
  57.     // 获取用户信息
  58.     GetInfo({ commit }) {
  59.       return new Promise((resolve, reject) => {
  60.         getInfo().then(res => {
  61.           console.log('res::: ', res);
  62.           if (res.data.code === 0 || 200) {
  63.             const user = res.data.sysUser;
  64.             const roleType = res.data.roleType;
  65.             commit('SET_USERINFO', user);
  66.             // roleType 用户所用的权限级别 1 普通用户  2 项目经理  3 部门管理员  4 综合部管理员  5  部门领导  -1 项目运维管理员
  67.             setStore('ROLE_TYPE', roleType);
  68.             if (res.data.roles) { // 验证返回的roles是否为真
  69.               commit('SET_ROLES', res.data.roles);
  70.               commit('SET_PERMISSIONS', res.data.permissions);
  71.             } else {
  72.               commit('SET_ROLES', ['ROLE_DEFAULT']);
  73.             }
  74.             resolve();
  75.           } else {
  76.             reject(error);
  77.           }
  78.         }).catch(error => {
  79.           reject(error);
  80.         })
  81.       })
  82.     },
  83.     GenerateRoutes({ commit }) {
  84.       return new Promise((resolve, reject) => {
  85.         // 向后端请求路由数据
  86.         getRouters().then(res => {
  87.           if (res.data.code === 0 || 200) {
  88.             const sdata = JSON.parse(JSON.stringify(res.data.routes));
  89.             console.log('sdata::: ', sdata);
  90.             let newRouters = filterAsyncRouter(sdata);
  91.             // 连接公共路由
  92.             const sidebarRoutes = routes.concat([...newRouters]);
  93.             commit('SET_ROUTE', sidebarRoutes);
  94.             resolve(sidebarRoutes);
  95.           } else {
  96.             reject(error);
  97.           }
  98.         }).catch(error => {
  99.           reject(error);
  100.         })
  101.       })
  102.     },
  103.     // 退出系统
  104.     LogOut({ commit, state }) {
  105.       return new Promise((resolve, reject) => {
  106.         logout(state.token).then(() => {
  107.           commit('SET_TOKEN', '')
  108.           commit('SET_ROLES', [])
  109.           commit('SET_PERMISSIONS', [])
  110.           clearStore('token');
  111.           clearStore('userInfo')
  112.           resolve()
  113.         }).catch(error => {
  114.           reject(error)
  115.         })
  116.       })
  117.     },
  118.   },
  119.   modules: {
  120.   }
  121. })
  122. const loadView = (view) => {
  123.   // 路由懒加载
  124.   return () => import(`@/views${view}`);
  125. };
  126. function filterAsyncRouter(routes) {
  127.   return routes.filter((route) => {
  128.     if (Array.isArray(route.children) && route.children.length > 0) {
  129.       // 如果该路由含有子路由时,递归调用该函数
  130.       route.children = filterAsyncRouter(route.children);
  131.     }
  132.     if (route.component) {
  133.       // Layout ParentView 组件特殊处理
  134.       if (route.component === "Layout") {
  135.         route.component = Layout;
  136.       } else {
  137.         // 路由组件懒加载
  138.         route.component = loadView(route.component);
  139.       }
  140.     }
  141.     return true;
  142.   })
  143. }
复制代码
  由于接口返回的 component 是字符串, 需手动封装函数转换成组件
  公共路由如下
router/index.js
  1. // router/index.js
  2. import Vue from 'vue'
  3. import VueRouter from 'vue-router'
  4. import Layout from '@/Layout/index.vue'
  5. Vue.use(VueRouter)
  6. // 公共路由
  7. export const routes = [
  8.   {
  9.     path: '/',
  10.     name: 'redirect',
  11.     component: Layout,
  12.     hidden: true, // 隐藏菜单
  13.     redirect: "/homePage", // 用户在地址栏输入 '/' 时会自动重定向到 /homePage 页面
  14.   },
  15.   {
  16.     path: '/homePage',
  17.     component: Layout,
  18.     redirect: "/homePage/index",
  19.     meta: {
  20.       title: "首页",
  21.     },
  22.     children: [
  23.       {
  24.         path: 'index',
  25.         name: 'homePageIndex',
  26.         meta: {
  27.           title: "首页",
  28.         },
  29.         component: () => import('@/views/homePage/index.vue')
  30.       }
  31.     ]
  32.   },
  33.   {
  34.     path: '/login',
  35.     component: () => import('@/views/login.vue'),
  36.     hidden: true
  37.   },
  38.   {
  39.     path: '/404',
  40.     component: () => import('@/views/error/404.vue'),
  41.     hidden: true
  42.   },
  43.   {
  44.     path: '/401',
  45.     component: () => import('@/views/error/401.vue'),
  46.     hidden: true
  47.   },
  48. ]
  49. const router = new VueRouter({
  50.   base: process.env.BASE_URL,
  51.   routes
  52. })
  53. export default router
复制代码
文件布局如下

页面菜单渲染

   左侧菜单实现参考链接: Elemnt-UI + 递归组件实现背景管理体系左侧菜单
  前端单独实现动态路由参考毗连: 前端单独实现 vue 动态路由
  总结

通过以上步调,你可以实现一个完备的动态路由权限管理体系:


  • 后端接口返回路由设置:获取用户的权限信息及路由信息。
  • 动态加载组件:使用异步组件方式加载指定路径的组件。
  • 动态添加路由:根据权限信息动态添加路由。
  • 路由守卫:使用 router.addRoutes() 或 router.addRoute() 添加路由。
如许可以确保应用根据用户的权限动态加载相应的路由,增强安全性与机动性。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表