接口数据查看,业务方式查看
- 给角色分配路由权限,然后路由信息上meta就会有哪些角色可以访问的数组。就是说一个路径,哪些角色可以访问,都在meta下的roles里面保存着
- 接着用户角色分配
前端代码实现
- 核心代码
通过用户信息上用户的角色数组和路由meta上的角色数组是否包含用户角色,来过滤用户是否有权限访问这个路由路径
/**
* Use meta.role to determine if the current user has permission
*
* @param roles 用户角色集合
* @param route 路由
* @returns
*/
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (route.meta && route.meta.roles) {
// 角色【超级管理员】拥有所有权限,忽略校验
if (roles.includes("ROOT")) {
return true;
}
return roles.some((role) => {
if (route.meta?.roles) {
return route.meta.roles.includes(role);
}
});
}
return false;
};
permission.ts代码
import { RouteRecordRaw } from "vue-router";
import { constantRoutes } from "@/router";
import { store } from "@/store";
import MenuAPI from "@/api/menu";
import { RouteVO } from "@/api/menu/model";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layout/index.vue");
/**
* Use meta.role to determine if the current user has permission
*
* @param roles 用户角色集合
* @param route 路由
* @returns
*/
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (route.meta && route.meta.roles) {
// 角色【超级管理员】拥有所有权限,忽略校验
if (roles.includes("ROOT")) {
return true;
}
return roles.some((role) => {
if (route.meta?.roles) {
return route.meta.roles.includes(role);
}
});
}
return false;
};
/**
* 递归过滤有权限的动态路由
*
* @param routes 接口返回所有的动态路由
* @param roles 用户角色集合
* @returns 返回用户有权限的动态路由
*/
const filterAsyncRoutes = (routes: RouteVO[], roles: string[]) => {
const asyncRoutes: RouteRecordRaw[] = [];
routes.forEach((route) => {
const tmpRoute = { ...route } as RouteRecordRaw; // 深拷贝 route 对象 避免污染
if (hasPermission(roles, tmpRoute)) {
// 如果是顶级目录,替换为 Layout 组件
if (tmpRoute.component?.toString() == "Layout") {
tmpRoute.component = Layout;
} else {
// 如果是子目录,动态加载组件
const component = modules[`../../views/${tmpRoute.component}.vue`];
if (component) {
tmpRoute.component = component;
} else {
tmpRoute.component = modules[`../../views/error-page/404.vue`];
}
}
if (tmpRoute.children) {
tmpRoute.children = filterAsyncRoutes(route.children, roles);
}
asyncRoutes.push(tmpRoute);
}
});
return asyncRoutes;
};
// setup
export const usePermissionStore = defineStore("permission", () => {
// state
const routes = ref<RouteRecordRaw[]>([]);
// actions
function setRoutes(newRoutes: RouteRecordRaw[]) {
routes.value = constantRoutes.concat(newRoutes);
}
/**
* 生成动态路由,就是生成用户可以访问的路由,核心就是角色匹配
*
* @param roles 用户角色集合
* @returns
*/
function generateRoutes(roles: string[]) {
return new Promise<RouteRecordRaw[]>((resolve, reject) => {
// 接口获取所有路由
MenuAPI.getRoutes()
.then((data) => {
// 过滤有权限的动态路由
const accessedRoutes = filterAsyncRoutes(data, roles);
setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch((error) => {
reject(error);
});
});
}
/**
* 获取与激活的顶部菜单项相关的混合模式左侧菜单集合
*/
const mixLeftMenus = ref<RouteRecordRaw[]>([]);
function setMixLeftMenus(topMenuPath: string) {
const matchedItem = routes.value.find((item) => item.path === topMenuPath);
if (matchedItem && matchedItem.children) {
mixLeftMenus.value = matchedItem.children;
}
}
return {
routes,
setRoutes,
generateRoutes,
mixLeftMenus,
setMixLeftMenus,
};
});
// 非setup
export function usePermissionStoreHook() {
return usePermissionStore(store);
}
路由守卫实现权限路由添加到可访问的路由表中
hasroles 状态这个挺关键的,主要标识是否添加过权限路由,也代表用户角色
export function setupPermission() {
// 白名单路由
const whiteList = ["/login"];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const hasToken = localStorage.getItem(TOKEN_KEY);
if (hasToken) {
if (to.path === "/login") {
// 如果已登录,跳转首页
next({ path: "/" });
NProgress.done();
} else {
const userStore = useUserStore();
const hasRoles =
userStore.user.roles && userStore.user.roles.length > 0;
//hasRoles 可以判断是否添加过生成的权限路由,如果有直接跳转页面,没有再去添加权限路由
if (hasRoles) {
// 未匹配到任何路由,跳转404
if (to.matched.length === 0) {
from.name ? next({ name: from.name }) : next("/404");
} else {
next();
}
} else {
// 还没添加权限路由,这里实现添加下
const permissionStore = usePermissionStore();
try {
const { roles } = await userStore.getUserInfo(); //请求到用户角色,之后会作为状态写入store标识是否添加过权限路由
const accessRoutes = await permissionStore.generateRoutes(roles);
accessRoutes.forEach((route: RouteRecordRaw) => {
router.addRoute(route);
});
next({ ...to, replace: true });
} catch (error) {
// 移除 token 并跳转登录页
await userStore.resetToken();
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
// 未登录可以访问白名单页面
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
}
总结
就是通过后台路由绑定角色,角色信息记录在meta里面,meta里面的roles代表了这个路径哪些角色可以访问。
第二再用户绑定角色,用户绑定的角色是否存在某个路由的meta下的roles,有则表示可以访问该路由,没有则表示不能访问该路由。