# 一、动态路由生成

# 方案一(前期)

前端主控(直接根据角色处理动态路由)

  • 缺点:路由表与角色分配不能很好的进行动态处理

核心

利用 后端发来的 menus 数组 与 划分的动态路由的 name 标识 进行一一比对。

# 1、路由划分

  • router / index.js
// 划分
// 动态路由asyncRoutes
// 静态路由constantRoutes
  • router / modules
# 对动态路径进行模块化处理
# power.js
# teaching.js
# other.js

# 2、动态路由筛选

  • store / modules / permission.js
// 利用递归算法、角色条件筛选并生成动态路由
// generateRoutes()
// filterAsyncRoutes()
// hasPermission()

// 并存储
// 当前用户的动态路由addRoutes
// 当前用户的全部路由routes

# 3、路由守卫生成

  • @ / permission.js
// 根据
// ① 登录时后端返回的用户角色信息role(参数1)
// ② router / index.js 中划分的asyncRoutes(参数2)
// 动态生成路由
import Layout from '@/layout'

export default {
  path: '/power',
  component: Layout, // 保持Layout架子渲染
  alwaysShow: true, // 当children只有一个时,显示根路由
  meta: { title: '权限管理', icon: 'form', roles: ['super-admin', 'root'] },
  children: [
    {
      path: 'userControl',
      name: 'userControl',
      component: () => import('@/views/power/userControl'),
      meta: { title: '用户管理', icon: 'user', roles: ['super-admin', 'root'] }
    },
    {
      path: 'roleControl',
      name: 'roleControl',
      component: () => import('@/views/power/roleControl'),
      meta: {
        title: '角色管理',
        icon: 'nested',
        roles: ['super-admin', 'root']
      }
    },
    {
      path: 'menuControl',
      name: 'menuControl',
      component: () => import('@/views/power/menuControl'),
      meta: { title: '菜单管理', icon: 'example', roles: ['super-admin'] }
    }
  ]
}

# 方案二(后期)

后端主控(根据 RBAC 权限从数据表中生成路由表供前端使用)

  • 优点:登录时后端以知道用户角色信息,使用路由的 name 代替 role 进行动态路由生成
{
  "data": {
    "id": 43,
    "username": "李大庄",
    "email": "[email protected]",
    ……
    "token": "Bearer eyJhbGciOiJI……",
    // 用户角色
    "role": ["super-admin"]
  },
  // 菜单权限
  "menus": ["userControl","roleControl","menuControl",……],
  // 按钮权限(自由度比较高,前后端商量好)
  // "rights": ["view", "edit", "add", "delete"]
}
  • @ / permission.js
// 进行逻辑处理、数据转换、动态添加(难点🤔)

# 1、路由划分

  • router / index.js
// 划分
// 动态路由asyncRoutes
// 静态路由constantRoutes
  • router / modules
# 对动态路径进行模块化处理
# power.js
# teaching.js
# other.js

# 2、动态路由筛选

  • store / modules / permission.js
import { asyncRoutes, constantRoutes } from '@/router'

export default {
  namespaced: true,
  state: {
    routes: constantRoutes // 路由菜单
  },
  mutations: {
    setRoutes(state, filterRoutes) {
      state.routes = [...constantRoutes, ...filterRoutes]
    },
    removeRoutes(state) {
      state.routes = constantRoutes
    }
  },
  actions: {
    filterRoutes(context, menus) {
      // filter返回的是一个通过测试的数组
      const filterRoutes = asyncRoutes.filter((route) =>
        // 使用some对每一个route的name属性进行检查
        menus.some((name_item) => name_item.includes(route.children[0].name))
      )

      // 添加路由到路由配置中
      router.addRoutes([
        ...filterRoutes,
        // 解决刷新后404问题🤔
        { path: '*', redirect: '/404', hidden: true }
      ])

      // 动态路由合并
      context.commit('setRoutes', filterRoutes)
    }
  }
}
  • @ / layout / components / Sidebar / index.vue

菜单生成:数据获取从 router 改为使用 vuex 中的数据

export default {
  computed: {
    routes() {
      // return this.$router.options.routes

      // 解决菜单显示异常的问题🤔
      return this.$store.state.permission.routes
    }
  }
}
  • @ / router / index.js
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}
  • @ / store / modules / user.js

路由生成:退出登录时 重置路由

import { resetRouter } from '@/router'

// 退出登录
logOutActions({ commit }) {
  commit('RESET_STATE')
  // 重置token
  commit('REMOVE_TOKEN')
  // 重置路由
  resetRouter()
}

思考

1、为何动态添加好用, 但是左侧导航不出现?

  网页刚打开, 左侧导航已经使用路由对象规则数组, 动态权限是靠网络请求异步回来添加的

  而this.$router.options.routes是静态的,想要它能够丢失再重新生成则可以使用 Vuex 来存储路由数据

2、退出登录时,要做哪些事情?

  清除本地的 token, 清除 vuex 里 token, 重置路由规则对象的数组, 然后重新跳转回到登录页

# 3、路由守卫生成

  • @ / permission.js
// 前置路由守卫
router.beforeEach(async (to, from, next) => {
  // 显示进度条效果
  NProgress.start()

  // 获取到 token
  const token = store.getters.token

  // 如果存在 token
  if (token) {
    if (to.path === '/login') next('/') // 如果存在 token,访问的是登录页面,直接跳转到主页
    else {
      if (!store.getters.name) {
        const menus = await store.dispatch('user/getUserInfoActions')
        // 根据menu过滤出 用户可访问的动态路由,并对路由进行合并
        const filterRoutes = await store.dispatch('permission/filterRoutes', menus)

        // 解决菜单显示异常的问题
        // router.options.routes = [...constantRoutes, ...filterRoutes]

        // 解决刷新后白屏问题🤔
        next({
          path: to.path, // next()重定向:保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      }
      next() // 如果存在 token,访问的是其他页面,直接放行
    }
  } else {
    if (whiteList.includes(to.path)) next() // 如果不存在 token,访问的是白名单内容,直接放行
    else next('/login') // 没有 token,且不是白名单页面,跳转到登录页面
  }
})

# 方案三(后端路由主控)

后端主控(根据 RABC 权限从数据表中生成路由表供前端使用)

缺点:

  • 路由的 path 和 component 名称未知,不利于前端开发
  • 路由表要经过 前端二次处理
  • 前端的 tree 数据处理成本高

路由表示例

  后端路由表经过前端处理的最终结构(当然:数据是动态的、并且要动态的添加到 vue router 中)。

  • 路由表
import Layout from '@/layout'

export default {
  path: '/power',
  component: Layout, // 保持Layout架子渲染
  alwaysShow: true, // 当children只有一个时,显示根路由
  meta: { title: '权限管理', icon: 'form', roles: ['super-admin', 'root'] },
  children: [
    {
      path: 'userControl',
      name: 'userControl',
      component: () => import('@/views/power/userControl'),
      meta: { title: '用户管理', icon: 'user', roles: ['super-admin', 'root'] }
    },
    {
      path: 'roleControl',
      name: 'roleControl',
      component: () => import('@/views/power/roleControl'),
      meta: {
        title: '角色管理',
        icon: 'nested',
        roles: ['super-admin', 'root']
      }
    },
    {
      path: 'menuControl',
      name: 'menuControl',
      component: () => import('@/views/power/menuControl'),
      meta: { title: '菜单管理', icon: 'example', roles: ['super-admin'] }
    }
  ]
}

# 二、动态侧边栏

  基于 element ui 的 NavMenu 导航菜单进行升级改造。

  • @ / layout / SideBar / SidbarItem.vue
// 进行递归组件改造
// 其子组件:AppLink、Item

# 三、权限管理与 api 接口

# 1、角色表

  • 用户管理页面 -- 更改角色
  • 角色管理页面 -- 数据铺设

# 2、权限表

  • 菜单管理页面 -- 数据铺设
  • 菜单管理页面 -- 编辑菜单

# 4、角色权限管理表(路由表)

  • 角色管理页面 -- 分配权限

    • 更改指定角色的权限

# 四、其他

# 1、树形结构数据

应用场景:侧边栏、菜单管理……

  • @ / utils / index.js
/**
 * 把扁平结构的数据, 转成树形控件
 * @param {*} list
 * @param {*} rootValue
 * @returns
 */
export function translateListToTree(list, rootValue) {
  // list: 整个数组, rootValue本次要查找的目标id -> 此函数为了找到rootValue目标id的下属们
  const treeData = [] // 装下属对象的
  list.forEach((item) => {
    if (item.pid === rootValue) {
      // 当前对象pid符合, 继续递归调用查找它的下属
      const children = transTree(list, item.id) // 返回item对象下属数组
      if (children.length) {
        item.children = children // 为item添加children属性保存下属数组
      }
      treeData.push(item) // 把当前对象保存到数组里, 继续遍历
    }
  })
  return treeData // 遍历结束, rootValue的id对应下属们收集成功, 返回给上一次递归调用children, 加到父级对象的children属性下
}
  • 使用示例
    async getPermissionList() {
      // 后端传来的扁平数据
      console.log(await getPermissionList())

      // 前端处理后的树形数据
      this.list = translateListToTree(await getPermissionList(), '0')
      console.log(this.list)
    }

分析

pid:表示的是改数据的节点状态

  • 若 pid: 0 代表为改数据为根节点
  • pid等于某个数据的id,代表两者成父子节点关系

# 2、按钮权限

  • directives.js
import Vue from 'vue'
Vue.directive('imgerror', {
  // 元素放到页面上类似于组件mouted
  inserted(el, binding) {
    if (!el.src) {
      el.src = binding.value
    }
    el.onerror = function () {
      this.src = binding.value
    }
  },
  // 当模板发生改变,触发这个方法
  update(el, binding) {
    console.log('update', el.src)
    if (!el.src) {
      el.src = binding.value
    }
  }
})

# 3、api 权限

更新于 : 8/7/2024, 2:16:31 PM