vue + vite + js 后台管理系统:权限控制+路由/菜单动态加载

目录

1. 路由问题

2. 权限问题

2.1 权限控制有哪些

2.2 获取权限列表

3. 动态菜单+路由 具体实现过程

3.1 通用路由

3.2 主页面布局(使用antd-vue)

3.2.1 布局文件

3.2.2 路由配置

 3.3 侧边栏布局

3.4 获取权限列表

3.4.1 根据角色过滤

3.4.2 后台获取路由

3.4.3 动态添加路由

 4. 总结


1. 路由问题

在实现后台管理系统的整体路由设计前,我们首先要知道:①路由是什么?②路由的分类;③路由为什么要动态加载?

前端路由:指的是url地址和组件间的一一对应关系,其本质是对url的hash值进行改变和监听,从而切换对应页面组件的dom结构。

路由的分类:静态路由、动态路由。静态路由是任何菜单权限下都能够查看的界面路由,而动态路由则是根据菜单权限动态生成的路由集合。

路由的动态加载:①中大型项目中,路由较多,后台导航目录运营人员可能会修改(增删改...)菜单。如果路由是写死的,此时前端就需要在路由表上进行添加,这样用户在点击时才能获取相应页面,比较麻烦。但是如果是动态路由呢?因为动态路由获取的永远是最新的菜单数据,可以一次性通过动态添加的方式加进去,后面就不需要关心路由的添加删除,只需要对所修改的路由的页面进行更新即可;②解决权限问题。

2. 权限问题

什么是权限?用户A登录后只能访问a页面,不能访问b页面,这个就是权限。对于前端来说,权限管理就是让用户只能看到自己拥有权限的页面和组件。

前端最常用的权限模型为RBAC(Role-Based Access List),即基于角色的权限控制,在用户和权限中间添加一层角色来间接赋予用户权限,从而让用户与权限解耦。

知道权限是什么后,你是否知道前端常见的权限控制有哪些?我们又该如何对权限进行验证?

2.1 权限控制有哪些

常用的前端权限控制有好几种,在此简单总结如下:

①菜单的控制(本文章的重点):在用户登录后,前端通过接口从后端获取权限数据,根据权限数据展示对应的菜单(侧边栏菜单...),用户点击菜单查看相关界面;

②页面的控制:若用户没有登录,手动在地址栏输入管理界面的地址,则跳转到登录界面;若用户已经登录但手动输入非权限内的地址,则跳转404界面;在代码层面,页面控制通过路由守卫可以轻松实现,在此不赘述。

③内容的控制:页面权限控制能做到让不同角色访问不同页面,但对于一些颗粒度更小的项目,比如在某些菜单的界面中,根据权限展示出可以操作的按钮,如增删改...如打工人可以上传休假申请但没有相应的批准按钮,对应的人事则可以进行批准...(随便举的一个例子> <),对于这类颗粒度更细的权限控制,后续重新更新博客进行说明。

④请求和响应的控制:如果用户通过非常规操作,如通过浏览器调试工具将某些禁用的按钮变成启用状态,此时发的请求也应该被前端所拦截。

2.2 获取权限列表

权限对应于用户,那么就会涉及到用户的登录。登录详细内容见另一篇博客:vue+vite+js后台管理系统:用户登录功能_sunnakay的博客-CSDN博客

整体流程简单介绍如下:

step1:用户登录。用户登录成功后服务器会返回一个token,拿到token后,前端会将token保存到本地并且保存到数据仓库vuex或pinia中,防止刷新丢失,接着前端会根据token再去拉取一个user_info的接口来获取用户的详细信息,如用户权限、用户角色等。

step2:获取权限列表(已经获取用户信息)。

step3:将权限列表数据进行结构化,转成 route路由结构 及 menu菜单结构

step4:将结构好的路由使用route.addRoute()添加到路由表中,最后完成动态渲染。

针对step2的获取权限列表,我模拟了获取权限列表的两种方式,详见第三节:

方式①:根据用户角色进行权限过滤,这种方式下路由不能经由后台动态新增或删除,因为全部路由都由前端处理。

方式②:向后端发送请求,获取动态权限列表,这种方式下后端给到前端的数据就已经是匹配对应用户的路由了,不需要前端再进行过滤(前端计算量减小),更易于修改和后期维护以及动态的增删改查。

具体选择哪种方式视具体工作项目而定。

3. 动态菜单+路由 具体实现过程

3.1 通用路由

定义一份通用路由,即不需要权限控制的路由:Home、404等等。

路由表里将常规路由()事先都写到路由表里,将需要权限的路由通过router.addRoute()动态添加到对应的子路由中。

3.2 主页面布局(使用antd-vue)

用户登录成功后,跳转主页面。后台管理系统页面的组成部分一般为:头部侧边栏(导航栏)页面主体部分以及可能会有尾部。下图为我截取的vben-admin的页面布局,可以看到包含Header、SideBar和Main(具体划分可能因人而异)。

 这里值得注意的地方是:由于页面的主体部分是显示路由的主要入口,因此我们要在对应的主页面上使用RouterView来展示路由页面。

3.2.1 布局文件

在src下新建layouts文件夹,里面存放公共页面,且直接通过路由配置。基本结构如下:

 入口index.vue中引入对应的侧边栏等组件,添加样式完成布局

3.2.2 路由配置

页面有了,需要在相应的路由中引入,否则没有效果。

 3.3 侧边栏布局

使用antd。侧边栏分为logo区,一级菜单和二级菜单,目前都是写死的数据(粗糙模拟一下...),后面需要对生成的动态菜单进行遍历。

 效果如下:

3.4 获取权限列表

3.4.1 根据角色过滤

思路如下:

首先获取用户角色(在登录成功后就已经获取并存至仓库和sessionStorage)和所有路由,在src/router/routes/index.js中定义路由集合,代码如下:

// 导入设置的所有路由
const modules = import.meta.glob('./modules/**/*.js', { eager: true });
// 将路由加入路由集合
const routeModuleList = [];
Object.keys(modules).forEach((key)=>{
    const mod = modules[key].default || {};
    const modList = Array.isArray(mod) ? [...mod] : [mod];
    routeModuleList.push(...modList)
})

export const asyncRoutes = [...routeModuleList];

注:某些路由的meta中存有roles属性,意味着只有其对应属性值的角色能够访问此路由,举例如下,只有拥有“super”角色的用户才能访问职员管理和岗位管理:

const setting = {
    name: '系统设置',
    path: '/setting',
    component: () => import('@/layouts/index.vue'),
    meta: {},
    children: [
        {
            name: '基础信息',
            path: 'base',
            component: () => import('@/views/setting/base/index.vue'),
            meta: {}
        },
        {
            name: '职员管理',
            path: 'user',
            component: () => import('@/views/setting/user/index.vue'),
            meta: {
                roles: ['super']
            }
        },
        {
            name: '岗位管理',
            path: 'role',
            component: () => import('@/views/setting/role/index.vue'),
            meta: {
                roles: ['super']
            }
        },
    ]
}

export default setting

 ②在src/stores/modules/permission.js下根据角色过滤路由集合

import { defineStore } from "pinia";
import { asyncRoutes } from "@/router/routes";
import { filter } from "@/utils/helper/treeHelper";
import { useUserStore } from "./user";
import { toRaw } from "vue";
import projectSetting from "@/setting/projectSetting";
import { getMenuList } from "@/api/menu";

export const usePermissonStore = defineStore('permission',{
    state: ()=>({
        // 菜单列表
        menuList: []
    }),
    getters:{
        getMenuList(state){
            return state.menuList
        }
    },
    actions:{
        setMenuList(menu){
            this.menuList = menu;
        },

        async buildRoutesAction() {
            // 获取角色
            const userStore = useUserStore();
            const roleList = toRaw(userStore.getRoleList);
            // 获取权限获取方式
            const permissionMode = projectSetting.permissionMode;
            let routes = []

            // 定义根据角色过滤路由的函数
            const filterByRole = (route)=>{
                const { meta = {} } = route;
                const { roles } = meta || [];
                if(!roles)return true
                return roleList.some((role) => roles.includes(role))
            }

            // 根据permissionMode选择权限过滤方法
            switch (permissionMode) {
                // 路由映射,根据角色过滤
                case "ROUTEMAPPING":
                    // 对路由进行过滤
                    routes = filter(asyncRoutes, filterByRole)
                    // 设置菜单,可能需要对菜单结构进行调整,视具体情况而定
                    this.setMenuList(routes);
                    break;
            
                // 后台获取
                case "BACK":
                    try {
                        // 通过后端接口获取路由
                        // 这里的routes是否需要进行结构转换具体视后端接口返回的数据结构而定
                        routes = await getMenuList();
                        this.setMenuList(routes)
                    } catch (error) {
                        console.log(error)
                    }
                    break;
            }
            return routes
        }
    }
})

3.4.2 后台获取路由

需要向后端接口发送请求,根据接口返回的数据进行路由的设置和菜单的设置,具体代码见3.4.1代码块。

我这里模拟的返回数据不需要再进行转换,实际情况中可能需要将后端返回的扁平化结构转换为路由所需要的树形结构,由于本文主要是梳理大致框架,所以这类细节问题不作展开,后续有时间的话可能会更新。

3.4.3 动态添加路由

在permission.js中我们完成了路由的获取(两种方式),接下来就需要动态添加路由。在需要动态添加路由的地方引入permissionStore并使用buildRoutesAction方法,然后使用router.addRoute()添加路由

如用户登录成功后需要跳转后台主页面,此时需要生成菜单,因此需要获取权限并生成对应路由列表,下列为src/stores/modules/user.js中登录和登录成功后的操作:

    async login(params) {
      try {
        // 发请求
        const result = await loginApi(params)
        // 保存token
        const { token, username } = result
        this.setToken(token)
        // 进行登录成功后跳转等工作
        return this.afterLoginAction(username)
      } catch (error) {
        return Promise.reject(error)
      }
    },

    async afterLoginAction() {
      // 获取用户信息
      const userInfo = await this.getUserInfoAction();
      // 动态匹配路由,加载菜单
      const permissionStore = usePermissonStore()
      const routes = await permissionStore.buildRoutesAction()
      routes.forEach((route) => {
        router.addRoute(route)
      })
      // 跳转主页面
      router.push('/dashboard')
      return userInfo
    },

这里我使用根据角色过滤的方式模拟了整个过程,登录的用户角色为admin,最后页面渲染出的侧边菜单栏如下,可以看到因为系统设置下的基础信息没有添加’super‘角色,而职员管理和岗位管理路由我们添加了'super'这一角色,因此在路由过滤阶段,这两个路由被过滤掉,在最终页面上也不做展示:

 4. 最后

动态路由影响整个项目的运行,弄清楚整体流程十分重要。在权限控制中还有颗粒度粗细之分,更详细的内容后续再进行补充吧。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值