通过账号权限列表实现前端路由权限

140 阅读4分钟

前言

最近失业在家无聊,想着开发一个网址导航,后端用nest写,前端用vue + naiveui,刚好写到后台管理的权限功能,就记录一下。初次写文章,文笔不好,请见谅。

思路

首先获取全部的vue页面,然后注册路由,登录时传一个权限列表比如这样 ['ADMIN', 'BLOCK_LIST'],前端定义好那些页面是对应哪个字段,然后通过匹配到没有的权限就把对应的路由删除,这样就实现了没有权限无法访问这个路径跳转到404页面。

开始

在vite中,我们可以使用glob获取文件,根据它我们可以获取到views下所有的vue页面,可以看一下我的文件夹格式。

image.png image.png

可以看到在views文件夹下最多有两层文件夹,两层文件夹主要是为了分组,第二张图可以看到后台管理的菜单栏有些有下拉菜单,vue文件都是使用大驼峰命名,文件夹使用小驼峰命名。

然后就可以根据glob获取vue页面,在router下面的configs文件夹下创建一个ts文件叫做fetchRoutes.ts

const modules = import.meta.glob('@/views/*/*.vue');
// 获取分组的vue页面
const groupModules = import.meta.glob('@/views/*/*/*.vue');

const routes: RouteRecordRaw[] = [];

for (const path in modules) {
  // 通过正则解析拿到的文件夹路径信息
  const pathList = path.match(/src\/views\/(\S+?)\/(\S+?)\.vue/) as string[];

  routes.push({
    // 用vue文件名作为路由名称
    name: pathList[2],
    // 用文件夹作为路径
    path: '/' + pathList[1],
    component: modules[path],
  });
}

// 获取分组路由
for (const path in groupModules) {
  // 通过正则解析拿到的文件夹路径信息
  const pathList = path.match(/src\/views\/(\S+?)\/(\S+?)\/(\S+?)\.vue/) as string[];

  routes.push({
    // 用vue文件名作为路由名称
    name: pathList[3],
    // 用文件夹作为路径,这里会使用两个文件夹的名字拼接成的路径,当然也可以不用,看个人
    path: '/' + pathList[1] + '/' + pathList[2],
    component: groupModules[path],
  });
}
export routes;

在router的configs文件夹下再创建一个base.ts文件夹,用来配置一些路由基础信息,比如首页和404页面,主要是配置重定向,因为路由都已经通过glob获取了。

// 配置基础路径
export const baseRoutes: RouteRecordRaw[] = [
  { path: '/', redirect: '/home' },
  { path: '/:pathMatch(.*)', redirect: { name: 'NotFound' } },
];

然后在router下面的index.ts文件里定义路由

import { baseRoutes } from './configs/base';
import routes from './configs/fetchRoutes';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [...baseRoutes, ...routes],
});

到了这一步,我们就把路由全部添加进去了,之后添加路由只需要在所用的ui框架的menu组件里添加对应的页面信息,比如我用的naiveui

然后开始添加路由权限,一般我们在登录的时候会把用户信息存起来,我这里存到了pinia,并且用了pinia-plugin-persistedstate做了持久化,同步到了localStorage。我把menu组件的数据也放到了pinia,方便等会删除路由的时候顺便匹配进行删除。

image.png image.png

接着在router的configs文件夹里的base.ts文件里增加权限所对应的页面

// 这里的类型是因为我用了pont生成的,没有的话可以去掉,或者手动写
export type adminRole = defs.AdminUserDto['adminRole'][number];

// 设置路由权限
export const authRouter: Record<adminRole, string[]> = {
  ADMIN: [''],
  BLOCK_LIST: [],
};

使用vuerouter的路由守卫来通过权限修改路由

// 检测是否根据权限删除路由
let isCheckRoute = false;

router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
  // 通过pinia获取用户数据里的权限
  const configStore = useConfigStore();
  // 如果是进入登录页面则不做操作
  if (to.name !== 'Login') {
    // 未登录跳回登录页
    if (!configStore.token) {
      return next({ name: 'Login' });
    } else if (!isCheckRoute) {
      // 用户数据里的权限列表
      const adminRole = configStore.userData?.adminRole;
      // 匹配没有权限的路由
      if (adminRole) {
        const allAuthList = Object.keys(authRouter);
        // 对比用户的权限和所有的权限列表,拿到差集就是用户没有的权限,删掉对应的路由就好
        const notAuthKeyList = allAuthList.filter(i => !adminRole.includes(i));
        notAuthKeyList.forEach(i => {
          authRouter[i].forEach(name => {
            if (router.hasRoute(name)) {
              router.removeRoute(name);
            }
            // 这里是ui组件的菜单组件对应的数据,我把自定义一个函数进行删除
            configStore.setNotShowMenu(name);
          });
        });
        isCheckRoute = true;
        // 当前路由使用的是未更改的routes,需要手动检测是否存在,不存在跳转到NotFound
        if (!router.hasRoute(to.name!)) {
          next({ name: 'NotFound' });
        }
      }
    }
  }

  return next();
});

这样登录后就实现了通过账号权限列表实现前端路由权限