本章使用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.ts和store/user/mutations.ts和store/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.ts和store/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();
});
篇尾
上述具体实现流程后期加强代码描述提供更好的详细理解。。。。