美文网首页vue
vue权限控制和动态路由

vue权限控制和动态路由

作者: 扶得一人醉如苏沐晨 | 来源:发表于2023-10-27 16:11 被阅读0次

一、拆分路由

将路由拆分为三部分:常量路由、需要异步加载的路由、任意路由

常量路由:就是不关用户是什么角色,都可以看见的路由

异步路由:未来会根据角色被过滤的路由
一般这种情况是所有菜单已知的情况下,定义出所有菜单的全量路由,后面根据后端返回的权限菜单,来过滤这个全量路由,达到不同角色展示不同的菜单权限的效果

任意路由:当路径出现错误的时候重定向404

src/router/index.js写入如下代码

//引入Vue|Vue-router
import Vue from "vue";
import Router from "vue-router";

//使用路由插件
Vue.use(Router);

/* 引入最外层骨架的一级路由组件*/
import Layout from "@/layout";

//常量路由:就是不关是什么角色,都可以看见的路由
//什么角色(超级管理员,普通员工):登录、404、首页
export const constantRoutes = [
  {
    path: "/login",
    component: () => import("@/views/login/index"),
    hidden: true,
  },

  {
    path: "/404",
    component: () => import("@/views/404"),
    hidden: true,
  },

  {
    path: "/",
    component: Layout,
    redirect: "/dashboard",
    children: [
      {
        path: "dashboard",
        name: "Dashboard",
        component: () => import("@/views/dashboard/index"),
        meta: { title: "首页", icon: "dashboard" },
      },
    ],
  },
];

//异步理由:不同的(角色),需要过滤筛选出的路由,称之为异步路由
//有的角色可以看见产品管理、有的可以看见订单管理
export const asyncRoutes = [
 {
    path: "/product", //产品管理
    name: "product",
    component: () => import("@/views/product/Index.vue"),
    redirect: "/product/list",
    meta: {
      title: "产品管理",
    },
    children: [
      {
        path: "list", //访问路径: /product/list
        name: "list",
        component: () => import("@/views/product/list/Index.vue"),
        meta: {
          title: "产品列表",
        },
      },
      {
        path: "category",
        name: "category",
        component: () => import("@/views/product/category/Index.vue"),
        meta: {
          title: "产品分类",
        },
      },
    ],
  },
  {
    path: "/order", //订单管理
    name: "order",
    component: () => import("@/views/order/Index.vue"),
    redirect: "/order/order-list",
    meta: {
      title: "订单管理",
    },
    children: [
      {
        path: "order-list",
        name: "order-list",
        component: () => import("@/views/order/list/Index.vue"),
        meta: {
          title: "订单列表",
        },
      },
      {
        path: "contract",
        name: "contract",
        component: () => import("@/views/order/contract/Index.vue"),
        meta: {
          title: "订单审核",
        },
      },
    ],
  },
];

//任意路由:当路径出现错误的时候重定向404
export const anyRoutes = { path: "*", redirect: "/404", hidden: true };

const createRouter = () =>
  new Router({
    // mode: 'history', // require service support
    scrollBehavior: () => ({ y: 0 }),
    //因为注册的路由是‘死的’,‘活的’路由如果根据不同用户(角色)可以展示不同菜单
    routes: [...constantRoutes, ...anyRoutes],
  });

const router = createRouter();

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter();
  // router.matcher是比较核心的一个属性。对外提供两个方法match(负责route匹配), addRoutes(动态添加路由)。
  // 对router.matcher属性做修改,即新的routes就会替换老的routes, 其实就是replaceRoutes()的含义(但是官方没有提供这个API)。
  router.matcher = newRouter.matcher; // reset router
}

export default router;

router.matcher是比较核心的一个属性

router.matcher属性做修改,即新的routes就会替换老的routes

二、根据登录用户获取用户信息,并且动态添加路由

思路

  • 用户登录成功,将token存储进入vuex
  • 调用getUserInfo接口,获取用动态路由dyRoutes
  • 根据dyRoutes过滤我们定义的asyncRoutes得到过滤后的动态filterRoutes
  • filterRoutes存到vuex
  • 调用resetRouter()方法,将路由恢复到初始状态
  • 将比对后的路由通过router.addRoutes(filterRoutes);加入路由配置中

2.1、封装vuex的user模块 src/store/modules/user.js

代码如下

/* 引入前端定义的路由,和重置路由的方法 */
import { asyncRoutes, resetRouter } from "@/router";
/* 引入路由对象 */
import router from "@/router";
/* 深拷贝方法 */
import cloneDeep from "lodash/cloneDeep";
/* 引入登录api */
import { loginByUsername } from "@/api/login";
/* 获取用户信息的接口 */
import { getUserInfo } from "@/api/user";

//asyncRoutes前端定义的路由  dyRoutes后端返回的路由
const computedAsyncRoutes = (asyncRoutes, dyRoutes) => {
  //定义存储匹配好的路由容器
  let filterRoutes = [];
  // 深拷贝前端路由
  let asyncRoutesTmp = cloneDeep(asyncRoutes);
  asyncRoutesTmp.forEach((one) => {
    dyRoutes.forEach((two) => {
      if (one.name === two.name) {
        //继续判断下级菜单 children
        if (two.children && two.children.length > 0) {
          one.children = computedAsyncRoutes(one.children, two.children);
        }
        filterRoutes.push(one);
      }
    });
  });
  return filterRoutes;
};
const user = {
  state: {
    /* 用户的按钮权限 */
    permissions: [],
    /* 用户角色 */
    roles: [],
    /* 根据asyncRoutes和服务端返回的路由过滤出来的路由 */
    filterRoutes: [],
    /* token */
    token: "",
  },
  actions: {
    // 根据用户名登录
    LoginByUsername({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        loginByUsername(userInfo.username, userInfo.password)
          .then((response) => {
            /* 存储token */
            commit("SET_TOKEN", response.data.token);
            resolve();
          })
          .catch((error) => {
            reject(error);
          });
      });
    },
    // 查询用户信息
    GetUserInfo({ commit }) {
      return new Promise((resolve, reject) => {
        getUserInfo()
          .then((res) => {
            const data = res.data.data || {};
            /* 存储用户角色 */
            commit("SET_ROLES", data.roles || []);
            /* 存储用户按钮权限 */
            commit("SET_PERMISSIONS", data.permissions || []);
            /* 存储比对后的动态路由 */
            commit(
              "SET_FILTER_ROUTES",
              computedAsyncRoutes(asyncRoutes, data.routes)
            );
            resolve(data);
          })
          .catch(() => {
            reject();
          });
      });
    },
  },
  mutations: {
    /* 存储token */
    SET_TOKEN: (state, token) => {
      state.token = token;
    },
    /* 存储按钮权限 */
    SET_PERMISSIONS: (state, permissions) => {
      state.permissions = permissions;
    },
    /* 存储用户角色信息 */
    SET_ROLES: (state, roles) => {
      state.roles = roles;
    },
    //存储最终比对后的路由信息
    SET_FILTER_ROUTES: (state, filterRoutes) => {
      state.filterRoutes = filterRoutes;
      //添加路由之前 清空路由实例内容
      resetRouter();
      //将比对后的路由通过router.addRoutes加入路由配置中
      router.addRoutes(filterRoutes);
    },
  },
};
export default user;

2.2、引入user模块 src/store/index.js

import Vue from "vue";
import Vuex from "vuex";
import user from "./modules/user";
import getters from "./getters";

Vue.use(Vuex);
const store = new Vuex.Store({
  modules: {
    user,
  },
  getters,
});

export default store;

三、设置全局前置守卫 router.beforeEach,当用户信息、路由信息丢失时重新获取,解决页面刷新时数据丢失问题

3.1、src/permission.js

const whiteList = ["/login"];
/* 引入路由对象 */
import router from "@/router";
/* 引入store对象 */
import store from "@/store";
router.beforeEach(async (to, from, next) => {
  /* 如果有token */
  if (store.state.user.token) {
    /* 用户去登录页--直接跳转到layout首页 */
    if (to.path === "/login") {
      next({ path: "/" });
    } else {
      //判断当前存储的vuex里面是否已经有动态路由了
      if (store.state.user.filterRoutes.length != 0) {
        //有动态路由,放行
        next();
      } else {
        //没有路由
        try {
          /* 获取动态路由--并添加通过addRoutes到router实例 */
          await store.dispatch("GetUserInfo");
          // hack方法 确保addRoutes已完成
          // 其实在路由守卫中,只有next()是放行
          // 其他的诸如:next('/logon') 、 next(to) 或者 next({ ...to, replace: true })都不是放行,
          // 而是:中断当前导航,执行新的导航,会重新进入router.beforeEach。
          // next({ ...to, replace: true })中的replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。
          next({ ...to, replace: true });
        } catch (error) {
          next(`/login?redirect=${to.path}`);
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next();
    } else {
      next(`/login?redirect=${to.path}`);
    }
  }
});

四、总结

1、router.addRoutes()函数的作用:给路由器添加新的路由,并且是在已有的路由上再添加路由,并不会覆盖原有路由,所以我们在重复添加路由的时候,可能就会出现警告 [vue-router] Duplicate named routes definition: { name: "Dashboard", path: "/dashboard" },意思是你重复添加了 nameDashboard的路由。

2、为什么 router.addRoutes() 添加新路由后,需要手动修改router.options.routes
router.options是我们创建路由实例,传入的配置项,router.addRoutes动态添加路由后并不会修改router.options.routes,Vue这么设计的,所以需要手动修改 router.options.routes。

3、页面刷新后,vuex数据丢失,同样的动态添加的路由也会丢失,所以需要重新添加路由信息,并且刷新后直接访问动态添加的路由会出现白屏问题,因为动态路由是异步加载的,我们就直接访问了还没有加载的页面,所以会出现白屏问题,我们需要中断当前导航,执行新的导航,即重新访问一次路由才行。所以需要用到全局前置守卫 router.beforeEach重新获取数据,并且需要用到 next({...to, replace:true}) 重新访问一次路由才行。

4、next()是放行,其他的诸如:next('/logon')next(to) 或者 next({ ...to, replace: true })都不是放行,而是:中断当前导航,执行新的导航,会重新进入 router.beforeEach

5、next({ ...to, replace: true })中的replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。

6、一定要确保 addRoutes() 已经完成时,再执行下一次beforeEach((to, from, next)

7、如果守卫中没有正确的放行出口的话,会一直next({ ...to})进入死循环 !!!

8、当我们分配权限的时候、一定要注意子路由和父路由的关系,因为我们可能只勾选了子路由却没有勾选父路由,这种情况下是无法访问子路由的,因为父路由不存在。实际我们就应该直接将勾选的子路由上的父路由也保存下来,否则父路由没有的情况下,我们是无法访问子路由的,也添加不上。所以再好的办法是,直接将勾选的子路由上的父路由也传递给后台,保存下来。

  // 解决elementUI树形控件子节点勾选,但是半勾选父节点未被保存的的问题
const ids = Array.from(
    new Set([
      ...this.$refs.tree.getHalfCheckedKeys(),
      ...this.$refs.tree.getCheckedKeys(),
    ])
  ).join(",");

相关文章

  • vue-router的addRoutes的刷新404和重复路由的

    Vue后台管理项目经常有权限控制,不同角色使用不同权限,常用方法就是用addRoutes来动态添加路由,但有两个问...

  • 兰陵王-后台管理系统框架

    是一个基于Vue的前端系统管理框架,集成状态管理,动态路由,自主登录,权限控制,路由切换现场保留等功能 Build...

  • vue路由的介绍(二)--vue动态路由和get的传值

    vue动态路由和get的传值---->同属于路由的传参 1,vue动态路由: 动态路由的配置: ①,在配置路由时加...

  • 写一些关于vue的权限控制

    胡说八道一番 权限控制大概分为三种方式1.使用addRoutes动态添加路由,进行权限控制2.动态导航,控制入口,...

  • vue 使用addRoutes动态添加路由 刷新页面跳转404路

    在做vue项目时需要使用后端给的路由权限,处理路由动态添加到路由上,几经查找在 vue-Router中有个rou...

  • Vue实现动态路由

    通常我们在vue项目中都是前端配置好路由的,但在一些项目中我们可能会遇到权限控制,这样我们就涉及到动态路由的设置了...

  • vue-router 路由权限控制

    vue项目开发中经常需要对路由进行权限控制,比如只有登录才能访问某个路由这里分享一个简单是路由登录权限设置 废话不...

  • vue 权限控制

    根据后台控制vue页面和组件的访问 先将路由分为两种,需要权限访问的 routeMap, 不需要权限访问的rout...

  • vue后台系统路由菜单动态权限控制

    需求: 现有一个后台管理系统,涉及到用户权限菜单的问题,即不同的用户登录,根据权限的不同展示不同的菜单。 回答: ...

  • 动态路由

    思路: 1. 首先得设定一个layout主页面,动态路由只是动态子路由的权限控制。 2. 设立映射表,登录后...

网友评论

    本文标题:vue权限控制和动态路由

    本文链接:https://www.haomeiwen.com/subject/exguidtx.html