vue3动态路由权限管理

1,309 阅读4分钟

本章使用Vue3通过使用vuex存储用户权限路由+全局前置守卫 beforeEach实现动态路由权限管理

router路由

  • 模块:通过创建无权限制路由模块和有权限制模块分离路由,为后续动态权限路由做铺垫
  • 路由说明:所有的路由都由 getToken 获取 ,如果获取不到,则全部跳转到登录页面

1、无权限路由模块

router/constantModules/useManger.ts

import { RouteRecordRaw } from "vue-router";
const constantModules: Array<RouteRecordRaw> = [
  {
    path: "/login",
    name: "Login",
    component: import("@/views/login/index.vue"),
  }
];

export default noAccesRouter;

2、权限路由模块

router/permissionModules/example.ts

import { RouteRecordRaw } from "vue-router";
import Layout from "@/views/layout/index.vue";
const ExampleRouter: Array<RouteRecordRaw> = [
  {
    path: "/consult",
    name: "Consult",
    component: Layout,
    redirect: "/consult/list",
    meta: {
      title: "资讯管理",
      roles: ["admin"],
    },
    children: [
      {
        path: "/consult/list",
        component: import("@/views/layout/example/list.vue"),
        name: "List",
        meta: {
          title: "咨询列表",
          // icon: "list",
        },
      },
      {
        path: "/consult/create",
        component: () => import("@/views/layout/example/Create.vue"),
        name: "Create",
        meta: {
          title: "添加咨询",
          roles:["admin"],
          // icon: "add",
        },
      },
    ],
  },
];

export default ExampleRouter;

3、router/index.ts入口

公共路由及和读取到的无权路由创建路由实例

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Layout from "@/views/layout/index.vue";
//无需权限的路由
const constantFiles = require.context("./constantModules", true, /\.ts$/);
let constantRouter: Array<RouteRecordRaw> = [];
constantFiles.keys().forEach((key: string) => {
  if (key === "index.ts") return;
  constantRouter = constantRouter.concat(constantFiles(key).default);
});
//需要权限的路由
const perMissionFiles = require.context("./permissionModules", true, /\.ts$/);
let perMissionRouter: Array<RouteRecordRaw> = [];
perMissionFiles.keys().forEach((key: string) => {
  if (key === "indexe.ts") return;
  perMissionRouter = perMissionRouter.concat(perMissionFiles(key).default);
});

export const perMissionRoutes: Array<RouteRecordRaw> = [...perMissionRouter];
export const constantRoutes: Array<RouteRecordRaw> = [
  {
    path: "/",
    component: Layout,
    redirect: "/home",
    children: [
      {
        path: "/home",
        component: () => import("@/views/layout/home/index.vue"),
        name: "Home",
        meta: {
          title: "首页",
          // icon: '#icondashboard',
          // affix: true
        },
      },
    ],
  },
  ...constantRoutes,
];

const router = createRouter({
  history: createWebHistory(),
  routes: constantRoutes,
});

export default router;  

store状态管理

1、user模块

用户登录token及个人信息存储,这里列出
store/user/actions.tsstore/user/mutations.tsstore/user/state.ts

//store/user/actions.ts
import { ActionTree, ActionContext } from "vuex";
import { UserActionsTypes } from "./action-type";
import { UserMutationTypes } from "./mutation-type";
import request from "@/utils/request";
import { Mutations } from "./mutations";
import { IUserState, state } from "./state";
import { RootState } from "@/store";
import { ElMessage } from "element-plus";
import { removeToken, setToken } from "@/utils/cookies";
import { Login, getUser } from "@/apis/userApis";
import { LoginType, UserType } from "@/commontype/usertype";
import router from "@/router";
// const router = useRouter();
//action中派发到mutation的commit类型
type AugmentedActionContext = {
  commit<K extends keyof Mutations>(
    key: K,
    payload: Parameters<Mutations[K]>[1]
  ): ReturnType<Mutations[K]>;
} & Omit<ActionContext<IUserState, RootState>, "commit">;

//actions的类型 { login(){},...}
export type Actions = {
  [UserActionsTypes.ACTION_LOGIN](
    { commit }: AugmentedActionContext,
    userInfo: LoginType
  ): void;
  [UserActionsTypes.ACTION_GET_USER_INFO](
    { commit }: AugmentedActionContext,
    token: string
  ): void;
  [UserActionsTypes.ACTION_RESET_TOKEN](
    { commit }: AugmentedActionContext,
    payload: null | string
  ): void;
};

export const actions: ActionTree<IUserState, RootState> & Actions = {
  async [UserActionsTypes.ACTION_LOGIN](
    { commit }: AugmentedActionContext,
    userInfo: LoginType
  ) {
    await Login(userInfo).then((res) => {
      if (res.code === 200) {
        ElMessage.success("登录成功");
        commit(UserMutationTypes.SET_TOKEN, res.data.accessToken);
        setToken(res.data.accessToken);
        setTimeout(() => {
          router.push("/home");
        }, 1000);
      } else {
        ElMessage.error(res.msg);
      }
    });
  },
  async [UserActionsTypes.ACTION_GET_USER_INFO](
    { commit }: AugmentedActionContext,
    token: string
  ) {
    if (state.token === "") {
      throw Error("请先登录");
    }
    await getUser(token).then((res) => {
      let data = res[0];
      if (data) {
        commit(UserMutationTypes.SET_AVATAR, data.avatar);
        commit(UserMutationTypes.SET_EMAIL, data.emaill);
        commit(UserMutationTypes.SET_NAME, data.name);
        commit(UserMutationTypes.SET_INTRODUCTION, data.introduction);
        commit(UserMutationTypes.SET_ROLES, data.roles);
      } else {
        throw Error("请先登录");
      }
    });
  },
  async [UserActionsTypes.ACTION_RESET_TOKEN]({
    commit,
  }: AugmentedActionContext) {
    removeToken()
    await commit(UserMutationTypes.SET_TOKEN, "");
    await commit(UserMutationTypes.SET_ROLES, []);
  },
};

//store/user/mutations.ts
import { UserMutationTypes } from "./mutation-type";
import { IUserState, state } from "./state";
import { MutationTree } from "vuex";

//定义Mutations类型
export type Mutations<S = IUserState> = {
  [UserMutationTypes.SET_TOKEN](state: S, token: string): void;
  [UserMutationTypes.SET_AVATAR](state: S, avatar: string): void;
  [UserMutationTypes.SET_EMAIL](state: S, emaill: string): void;
  [UserMutationTypes.SET_INTRODUCTION](state: S, introduction: string): void;
  [UserMutationTypes.SET_NAME](state: S, name: string): void;
  [UserMutationTypes.SET_ROLES](state: S, roles: Array<string>): void;
};

//commit提交根据type修改state数据
export const mutations: MutationTree<IUserState> & Mutations = {
  [UserMutationTypes.SET_TOKEN](state: IUserState, token: string) {
    state.token = token;
  },
  [UserMutationTypes.SET_AVATAR](state: IUserState, avatar: string) {
    state.avatar = avatar;
  },
  [UserMutationTypes.SET_NAME](state: IUserState, name: string) {
    state.name = name;
  },
  [UserMutationTypes.SET_EMAIL](state: IUserState, emaill: string) {
    state.emaill = emaill;
  },
  [UserMutationTypes.SET_INTRODUCTION](
    state: IUserState,
    introduction: string
  ) {
    state.introduction = introduction;
  },
  [UserMutationTypes.SET_ROLES](state: IUserState, roles: Array<string>) {
    state.roles = roles;
  },
};

//store/user/state.ts
import { getToken } from "@/utils/cookies";

//用户信息类型
export interface IUserState {
  token: string;
  name: string;
  avatar: string;
  introduction: string;
  roles: string[];
  emaill: string;
}
//定义user仓库
export const state: IUserState = {
  token: getToken() || "",
  name: "",
  avatar: "",
  introduction: "",
  roles: [],
  emaill: "",
};

2、permission模块

permission模块匹配权限路由进行管理
这里简单列出store/user/actions.tsstore/user/mutations.ts

store/user/actions.ts
import { ActionTree } from "vuex";
import { PermissionActionType } from "./action-type";
import { IPermissionState } from "./state";
import { RootState } from "@/store";
import { Mutations } from "./mutations";
import router, { noAccesRoutes, perMissionRoutes } from "@/router";
import { PermissionMutationType } from "./mutation-type";
import { RouteRecordRaw } from "vue-router";

type AugmentedActionContext = {
  commit<
    T extends keyof Mutations 
  >(
    key: T,
    payload: Parameters<Mutations[T]>[1]
  ): ReturnType<Mutations[T]>;
};

export interface Actions {
  [PermissionActionType.ACTION_SET_ROUTES](
    { commit }: AugmentedActionContext,
    roles: string[]
  ): void;
}

//处理不是管理员的用户路由
export const filterNoAccesRouters = (
  routes: Array<RouteRecordRaw>,
  roles: string[]
) => {
  const res: RouteRecordRaw[] = [];
  routes.forEach((route) => {
    const r = { ...route };
    //判断获取无需admin下用户与当前用户的权限的路由
    if (hasPermission(roles, r)) {
      if (r.children) {
        r.children = filterNoAccesRouters(r.children, roles);
      }
      res.push(r);
    }
  });
  return res;
};

export const hasPermission = (roles: string[], route: RouteRecordRaw) => {
  if (route.meta && route.meta.roles) {
    return roles.some((role) => {
      if (route.meta?.roles !== undefined) {
        return (route.meta.roles as Array<string>).includes(role);
      }
    });
  } else {
    return true;
  }
};


export const actions: ActionTree<IPermissionState, RootState> & Actions = {
  [PermissionActionType.ACTION_SET_ROUTES](
    { commit }: AugmentedActionContext,
    roles: string[]
  ) {
    let accessedRoutes: Array<RouteRecordRaw> = [];
    if (roles.includes("admin")) {
      //如何是admin 那么所有的权限路由都可以访问
      accessedRoutes = accessedRoutes.concat(perMissionRoutes);
    } else {
      //如果用户不是admin那么通过对用户的权限去匹配所有需要权限访问的路由
      accessedRoutes = accessedRoutes.concat(
        filterNoAccesRouters(perMissionRoutes, roles)
      );
    }
    commit(PermissionMutationType.SET_ROUTES, accessedRoutes);
  },
};

store/user/mutations.ts
import { MutationTree } from "vuex";
import { PermissionMutationType } from "./mutation-type";
import { IPermissionState } from "./state";
import { RouteRecordRaw } from "vue-router";
import { noAccesRoutes } from "@/router";

export type Mutations<S = IPermissionState> = {
  [PermissionMutationType.SET_ROUTES](state: S, routes: RouteRecordRaw[]): void;
};

export const mutations: MutationTree<IPermissionState> & Mutations = {
  [PermissionMutationType.SET_ROUTES](
    state: IPermissionState,
    routes: RouteRecordRaw[]
  ) {
    state.routes = noAccesRoutes.concat(routes);
    state.dynamicRoutes = routes;
  },
};

3、index.ts入口

创建仓库实例

import { createStore, createLogger } from "vuex";
import { store as user, UserStore } from "./modules/user";
import { store as permission, PermissionStore } from "./modules/permission";
import { IUserState } from "./modules/user/state";
import { IPermissionState } from "./modules/permission/state";

//开发者模式使用createLogger打印
const debug = process.env.NODE_ENV !== "production";
const plugins = debug ? [createLogger({})] : [];

export interface RootState {
  user: IUserState;
  permission: IPermissionState;
}
//可提供外部组件调用的仓库类型
export type Store = UserStore<Pick<RootState, "user">> &
  PermissionStore<Pick<RootState, "permission">>;

export const store = createStore({
  plugins,
  modules: {
    user, //用户仓库
    permission, //路由权限
  },
});

export function useStore(): Store {
  return store as any;
}

权限控制

  • 创建./premission.js 在index.ts全局引入
  • 通过全局导航守卫beforeEach拦截并对路由进行动态添加
  • 用户登录成功通过token换取用户信息,拿用户权限来匹配路由存入仓库store.dispatch(PermissionActionType.ACTION_SET_ROUTES, roles);详细请看permission仓库模块
  • 获取仓库中匹配的路由遍历router.addRoute()动态添加用户权限下可访问的所有路由
import router from "./src/router";
import { useStore } from "@/store";
import { UserActionsTypes } from "@/store/modules/user/action-type";
import { ElMessage } from "element-plus/lib/components/message/index.js";
//加载动画
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { PermissionActionType } from "@/store/modules/permission/action-type";
const store = useStore();
router.beforeEach(async (to, form, next) => {
  NProgress.start();
  if (store.state.user.token) {
    if (to.path === "/login") {
      next({ path: "/home" });
      NProgress.done();
    } else {
      if (store.state.user.roles.length === 0) {
        try {
          //通过token获取用户信息
          await store.dispatch(
            UserActionsTypes.ACTION_GET_USER_INFO,
            store.state.user.token
          );
          const roles = store.state.user.roles; //用户权限 ["admin"]
          //修改用户权限
          store.dispatch(PermissionActionType.ACTION_SET_ROUTES, roles);
          //可访问的router
          store.state.permission.dynamicRoutes.forEach((route) => {
            router.addRoute(route);
          });
          next({ ...to, replace: true });
        } catch (err) {
          //删除token 让重新登录
          store.dispatch(UserActionsTypes.ACTION_RESET_TOKEN, undefined);
          ElMessage.error(err || "Has Error");
          next(`/login`);
          NProgress.done();
        }
      } else {
        next();
      }
    }
  } else {
    if (to.path === "/login") {
      next();
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done();
    }
  }
});
router.afterEach((to) => {
  NProgress.done();
});

篇尾

上述具体实现流程后期加强代码描述提供更好的详细理解。。。。