总结一下最近学习的后台管理系统的前端权限设计

2,977 阅读4分钟

刚到新公司,领导交代给了一个新项目,就是非常简易的后台管理系统,后端由于是刚毕业的,所以没有用什么已经搭建好的后台管理系统的框架,比如renren-fast啥的,后端都没有用,我自然只能陪他一点点的重新写,刚好我对这一块也不熟,正好用来练练手,学习一下,以下就是我本次开发总结的一点体会,记录一下。本文以思路为主,不会写出全部代码

基础工作

首先还是后台管理系统的基础工作,登录,侧边栏,导航栏什么的,因为给的时间实在太紧,我就直接用的网上已经有的基础框架 vue-admin-template 的,这些东西也没必要重复写,直接用现成的就好,主要还是总结一下权限相关。

菜单表设计

因为 vue-admin-template 框架中,侧边栏是根据路由生成的,所以我们只要用一个菜单表维护路由就行了,不需要单独再搞一个侧边栏管理,为了满足渲染路由所必须的参数,我们需要告诉后端我们都需要什么参数,一般情况下,具有一定开发经验的后端都知道要返回什么参数,但如果对方碰巧没啥经验,我们就要主动提出来了。

字段含义备注
title标题用于侧边栏标题展示
icon图标用于侧边栏图标展示
type类型区分目录/菜单/按钮
parentId父级id记录父子关系
name路由name路由必备
path地址地址栏的地址,用于跳转和展示
url模块路径模块位于文件夹的路径
identification授权标识用于权限判断,常见格式 crm:customer:list
hidden是否渲染在侧边栏有一些路由我们需要可以访问,又不想让它出现在侧边栏

*以上仅列出我们所必须的字段,像创建时间,创建人,排序等可以与后端协商按需求添加

角色分配

菜单表搞好之后,我们就可以开始开发角色列表,角色列表无非就是增删查改,这里仅记录自己碰到的几个小知识点。

给角色分配菜单时,保存的参数和回显

保存

大部分后台管理系统都是用的element-ui,而菜单展示一般会用element的el-tree组件,因为渲染路由的时候,需要有父子结构,我这里保存的时候会把选中的节点this.$refs.menuListTree.getCheckedKeys()和半选中的节点this.$refs.menuListTree.getHalfCheckedKeys()都保存下来

回显

因为保存的时候半选中的节点也给保存了下来,回显的时候如果给半选中的节点选中,它的子节点也会全部选中,如果要解决这个问题,我们只需要判断该节点是否是子节点就可以了

let menuId = res.data.menuId // 后端返回的id字符串 '1,3,4,5,8,9'
let _arr = menuId.split(",").map(item => {
  return +item; // 用加号是因为字符串分割的数组每一项都是字符串,需要转成数字
});
_arr.map(item => {
  //获取该id对应的tree节点
  let node = this.$refs.menuListTree.getNode(item);
  //判断该节点是否是子节点(即该节点是否是末级节点),是的话就设置选中状态
  if (node.isLeaf) {
    this.$refs.menuListTree.setChecked(node, true);
  }
});

路由守卫判断

前端做权限,主要靠的就是操作路由,这一块想了好久,事实证明,好记性不如赖笔头,想半天想不明白,写下来一会儿就搞明白了。

获取用户权限列表及菜单信息

这里贴上我的代码,里面注释了一些遇到的小难点

router.beforeEach(async (to, from, next) => {
  // vue-admin-template自带的进度条
  NProgress.start();

  // 设置浏览器标签标题
  document.title = getPageTitle(to.meta.title);

  // 获取token
  const hasToken = getToken();

  if (hasToken) {
    if (to.path === "/login") {
      // 如果已经登录,重定向至首页
      next({ path: "/" });
      NProgress.done();
    } else {
      const hasGetUserInfo = store.getters.name;
      if (!hasGetUserInfo) {
        try {
          // 如果没有用户信息则获取用户信息
          await store.dispatch("user/getInfo");
        } catch (error) {
          // 获取用户信息失败则清除token并跳转至首页
          await store.dispatch("user/resetToken");
          Message.error(error || "获取用户信息失败");
          next(`/login?redirect=${to.path}`);
          NProgress.done();
        }
      }
	  // 判断是否已经加载路由或者是否要访问白名单内的页面
      if (
        router.options.isAddDynamicMenuRoutes ||
        whiteList.indexOf(to.path) !== -1
      ) {
        next();
      } else {
        // 获取用户权限信息及菜单列表
        menuApi
          .getListById({ id: store.getters.userId })
          .then(res => {
            console.log(res);
            let menuList = res.data.menuList;
            let permissions = res.data.permission;
            // 我这里是后端返回所有的菜单,然后前端根据权限筛选出有权限的菜单
            // 筛选出有权限的路由或者是目录
            menuList = menuList.filter(item => {
              return (
                permissions.indexOf(item.identifying) > -1 ||
                item.parentId === 0
              );
            });
            // 将数据转化成路由结构
            menuList.map(item => {
              if (item.parentId === 0) {
                item.component = Layout;
              } else {
                item.component = _import(item.url);
              }
              item.meta = {
                title: item.title,
                icon: item.icon
              };
            });
            // 将路由转换成父子结构
            menuList = treeDataTranslate(menuList);
            console.log(menuList);
            menuList = menuList.filter(item => {
              return item.children;
            });
            // 在添加完动态路由之后再添加404路由,以防止页面在匹配动态路由之前先匹配404
            menuList.push({ path: "*", redirect: "/404", hidden: true });
            router.options.isAddDynamicMenuRoutes = true;
            router.addRoutes(menuList);
            // this.$router不是响应式的,所以手动将路由元注入路由对象
            router.options.routes.push(...menuList);
            // 下面这个我也不知道为什么要加,但是我知道不加刷新就会404😅
            if (from.name == null) {
              next(to);
            } else {
              next();
            }
          })
          .catch(err => {
            console.log(err);
            next(`/login?redirect=${to.path}`);
          });
      }
    }
  } else {
    /* has no token*/

    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next();
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`);
      NProgress.done();
    }
  }
});

才疏学浅,请各位大神多多指教,如果有哪里写的不好或者不详细的,请评论区留言