1
0

access.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import type { ExRouteRecordRaw, MenuRecordRaw } from '@vben/types';
  2. import type { RouteRecordRaw, Router } from 'vue-router';
  3. import { useAccessStore } from '@vben/stores';
  4. import { filterTree, mapTree, traverseTreeValues } from '@vben/utils';
  5. import { dynamicRoutes } from '../routes';
  6. // 登录页面路由 path
  7. const LOGIN_ROUTE_PATH = '/auth/login';
  8. // 不需要权限的页面白名单
  9. const WHITE_ROUTE_NAMES = new Set<string>([]);
  10. /**
  11. * 权限访问守卫配置
  12. * @param router
  13. */
  14. function configAccessGuard(router: Router) {
  15. router.beforeEach(async (to, from) => {
  16. const accessStore = useAccessStore();
  17. const accessToken = accessStore.getAccessToken;
  18. // accessToken 检查
  19. if (!accessToken) {
  20. // 明确声明忽略权限访问权限,则可以访问
  21. if (to.meta.ignoreAccess) {
  22. return true;
  23. }
  24. // 白名单路由列表检查
  25. if (WHITE_ROUTE_NAMES.has(to.name as string)) {
  26. return true;
  27. }
  28. // 没有访问权限,跳转登录页面
  29. if (to.fullPath !== LOGIN_ROUTE_PATH) {
  30. return {
  31. path: LOGIN_ROUTE_PATH,
  32. // 如不需要,直接删除 query
  33. query: { redirect: encodeURIComponent(to.fullPath) },
  34. // 携带当前跳转的页面,登录后重新跳转该页面
  35. replace: true,
  36. };
  37. }
  38. return to;
  39. }
  40. const accessRoutes = accessStore.getAccessRoutes;
  41. // 是否已经生成过动态路由
  42. if (accessRoutes && accessRoutes.length > 0) {
  43. return true;
  44. }
  45. // 生成路由表
  46. // 当前登录用户拥有的角色标识列表
  47. const userRoles = accessStore.getUserRoles;
  48. const routes = await generatorRoutes(userRoles);
  49. // 动态添加到router实例内
  50. routes.forEach((route) => router.addRoute(route));
  51. const menus = await generatorMenus(routes, router);
  52. // 保存菜单信息和路由信息
  53. accessStore.setAccessMenus(menus);
  54. accessStore.setAccessRoutes(routes);
  55. const redirectPath = (from.query.redirect || to.path) as string;
  56. const redirect = decodeURIComponent(redirectPath);
  57. return {
  58. path: redirect,
  59. replace: true,
  60. };
  61. });
  62. }
  63. /**
  64. * 动态生成路由
  65. */
  66. async function generatorRoutes(roles: string[]): Promise<RouteRecordRaw[]> {
  67. // 根据角色标识过滤路由表,判断当前用户是否拥有指定权限
  68. return filterTree(dynamicRoutes, (route) => {
  69. return hasVisible(route) && hasAuthority(route, roles);
  70. });
  71. }
  72. /**
  73. * 根据 routes 生成菜单列表
  74. * @param routes
  75. */
  76. async function generatorMenus(
  77. routes: RouteRecordRaw[],
  78. router: Router,
  79. ): Promise<MenuRecordRaw[]> {
  80. // 获取所有router最终的path及name
  81. const finalRoutes = traverseTreeValues(
  82. router.getRoutes(),
  83. ({ name, path }) => {
  84. return {
  85. name,
  86. path,
  87. };
  88. },
  89. );
  90. const menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
  91. // 路由表的路径写法有多种,这里从router获取到最终的path并赋值
  92. const matchRoute = finalRoutes.find(
  93. (finalRoute) => finalRoute.name === route.name,
  94. );
  95. // 转换为菜单结构
  96. const path = matchRoute?.path ?? route.path;
  97. const { meta, name: routeName, redirect, children } = route;
  98. const {
  99. badge,
  100. badgeType,
  101. badgeVariants,
  102. hideChildrenInMenu = false,
  103. icon,
  104. orderNo,
  105. target,
  106. title = '',
  107. } = meta || {};
  108. const name = (title || routeName || '') as string;
  109. // 隐藏子菜单
  110. const resultChildren = hideChildrenInMenu
  111. ? []
  112. : (children as MenuRecordRaw[]);
  113. // 将菜单的所有父级和父级菜单记录到菜单项内
  114. if (resultChildren && resultChildren.length > 0) {
  115. resultChildren.forEach((child) => {
  116. child.parents = [...(route.parents || []), path];
  117. child.parent = path;
  118. });
  119. }
  120. // 隐藏子菜单
  121. const resultPath = hideChildrenInMenu ? redirect : target || path;
  122. return {
  123. badge,
  124. badgeType,
  125. badgeVariants,
  126. icon,
  127. name,
  128. orderNo,
  129. parent: route.parent,
  130. parents: route.parents,
  131. path: resultPath,
  132. children: resultChildren,
  133. };
  134. });
  135. return menus;
  136. }
  137. /**
  138. * 判断路由是否有权限访问
  139. * @param route
  140. * @param access
  141. */
  142. function hasAuthority(route: RouteRecordRaw, access: string[]) {
  143. const authority = route.meta?.authority;
  144. if (!authority) {
  145. return true;
  146. }
  147. const authSet = new Set(authority);
  148. return access.some((value) => {
  149. return authSet.has(value);
  150. });
  151. }
  152. /**
  153. * 判断路由是否需要在菜单中显示
  154. * @param route
  155. */
  156. function hasVisible(route: RouteRecordRaw) {
  157. return !route.meta?.hideInMenu;
  158. }
  159. export { configAccessGuard };