【业务方案】管理后台权限方案个人记录

234 阅读4分钟

写在前面


  刚做完一个 PC 端后台人员权限的需求,期间遇到了蛮多问题,也被折磨得很难受。但是,做完需求之后。心里对于知识的掌握和一些原先半生不熟的东西有了挺多的“底气”。知识的学习终究还是要在实践中去体会。再次,复盘整理记录一下。

需求背景


  这次需求背景是一个 PC 端管理后台项目需要进行人员权限的管理,并且同一个账号跟不同渠道关联。也就是说,同一个账户,在不同渠道下面的权限是不一样的。同时,用户可以通过页面提供的切换渠道的按钮来切换渠道,进而刷新权限。

  这意味着权限数据是跟渠道关联的,每次切换数据,都需要重新刷新生成权限数据。

方案分析


  权限控制首先需要明确的是前端只能做辅助工作,重点工作在于后端。 因为无论前端怎么处理,总会存在非常规手段突破,那么后端的权限控制是不可缺少的。只有在后端具有权限控制的基础上,前端进行权限控制才有意义。

  前端的权限控制分为两个部分:明(菜单栏页面入口控制)和暗(路由列表控制)。重点是后者,因为如果路由列表没有控制的话,用户仍然可以通过直接输入 url 的方式进入没有权限的页面,违背了权限控制的目的。考虑到这里出现了新问题,那就是菜单页面的分类。

  比如说有一个菜单叫做「员工中心」,下面有2个页面:「员工管理」,「部门管理」两个页面入口。但是可以在「新建员工」页面里面,可以跳转到「新建员工」、「权限配置」页面。这个两个页面的入口不存在菜单栏中,而是在其他相关页面中。这就需要也将「新建员工」、「权限配置」页面纳入权限控制中。并且在业务角度就保证,新增页面时,放在对应的模块下面。

实际开发


  1. 完整菜单栏 menu.json

      因为需要根据不同的权限数据过滤出相应的菜单栏,这就意味着需要一份完整的菜单栏文件作为过滤的基础。这份文件可以存在前端,也可以存在后端。但是存储于前端更加方便,因为不需要调用后端接口。这里的关键是:每个权限控制页面都存在一个 webCode 字段,用来唯一标定该页面的权限控制码。这个码是后端生成的,唯一保持不变的。所以如果需要添加新的页面,那么需要后端提供 webCode, 前面添加才会生效。

      而对于通用页面,它的 webCode 字段值是空字符串,可以用来判断页面是否是通用页面。

[
  {
    "index": "1",
    "menuId": 140001,
    "parentId": 0,
    "label": "页面中心",
    "icon": "icon-page",
    "url": "/page-center",
    "orderNum": null,
    "type": "菜单",
    "webCode": "MENU_SHOW_PAGE_CENTER",
    "children": []
  },
  {
    "index": "2",
    "menuId": 140002,
    "parentId": 0,
    "label": "渠道中心",
    "icon": "icon-platform2",
    "url": "/platform/list",
    "orderNum": null,
    "type": "菜单",
    "webCode": "MENU_SHOW_CHANNEL_CENTER",
    "children": []
  },
  {
    "index": "3",
    "menuId": 140003,
    "parentId": 0,
    "label": "通用管理",
    "icon": "icon-cube",
    "url": "/platform/list",
    "orderNum": null,
    "type": "菜单",
    "webCode": "",
    "children": [
      {
        "index": "1",
        "menuId": 140004,
        "parentId": 140003,
        "label": "轮播管理",
        "icon": null,
        "url": "/manage/pagelist/banner",
        "orderNum": null,
        "type": "页面",
        "webCode": "",
        "children": []
      },
      {
        "index": "2",
        "menuId": 140005,
        "parentId": 140003,
        "label": "热区管理",
        "icon": null,
        "url": "/manage/pagelist/hotspot",
        "orderNum": null,
        "type": "页面",
        "webCode": "",
        "children": []
      },
      {
        "index": "3",
        "menuId": 140006,
        "parentId": 140003,
        "label": "坑位管理",
        "icon": null,
        "url": "/manage/pagelist/grid",
        "orderNum": null,
        "type": "页面",
        "webCode": "",
        "children": []
      },
      {
        "index": "4",
        "menuId": 140007,
        "parentId": 140003,
        "label": "列表多图管理",
        "icon": null,
        "url": "/manage/pagelist/chunk",
        "orderNum": null,
        "type": "页面",
        "webCode": "",
        "children": []
      }
    ]
  },
  {
    "index": "4",
    "menuId": 140008,
    "parentId": 0,
    "label": "员工中心",
    "icon": "icon-personal",
    "url": "/employee-center/",
    "orderNum": null,
    "type": "菜单",
    "webCode": "MENU_SHOW_STAFF_CENTER",
    "children": [
      {
        "index": "1",
        "menuId": 140010,
        "parentId": 140008,
        "label": "员工管理",
        "icon": null,
        "url": "/employee-center/employee-management",
        "orderNum": null,
        "type": "页面",
        "webCode": "MENU_SHOW_STAFF_MANAGE",
        "children": []
      },
      {
        "index": "2",
        "menuId": 140009,
        "parentId": 140008,
        "label": "部门管理",
        "icon": null,
        "url": "/employee-center/department-management",
        "orderNum": null,
        "type": "页面",
        "webCode": "MENU_SHOW_DEPT_MANAGE",
        "children": []
      }
    ]
  }
]

  1. 动态路由表

  与菜单栏相对应的,需要一个动态路由表。通用页面的路由作为静态路由,一直存在于 routes 中。但是对于权限控制页面,需要将具有权限的页面 url 塞入到动态路由表中,然后再通过 router.addRoutes() 这个方法将动态路由塞入路由表中。这个逻辑在每次切换渠道时,都要重新走一遍。

// 工厂函数:生成路由实例
const createRouter = () => {
  return new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: commonRoutes,
  })
}

const router = createRouter()

export const addDynamicRoutes = routes => {
  const newRouter = createRouter()

  newRouter.addRoutes([
    {
      path: '/',
      name: 'Base',
      component: Base,
      children: [...routes],
    },
  ])
	// 重点,将新的路由表替换旧的
  router.matcher = newRouter.matcher
}