-----------------------------------------------

登陆成功之后直接由后端返回异步路由表,然后前端直接通过addRoutes方法进行添加,并且生成侧边栏。

大致步骤:

  • 拦截路由
  • 取到后台路由数据
  • 添加并且保存路由(VUEX)

-----------------------------------------------

 该demo是在大神花裤衩的vue-admin-template上进行的修改调整。

https://github.com/PanJiaChen/vue-admin-template

-----------------------------------------------

具体实现步骤:

第一步:模拟路由数据(准备工作)

因该demo暂无数据交互,所以做的模拟数据,已完成后台数据交互的注意传递数据格式后可直接跳到第二步。

路由分为两种:constantRoutes 和 asyncRoutes

constantRoutes: 代表那些不需要动态判断权限的路由,如登录页、404、等通用页面。直接写在router/index.js中。

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '',
    component: Layout,
    redirect: '/index',
    name: '首页',
    meta: { title: 'XXX', icon: 'el-icon-s-help' },
    children: [
      {
        path: 'index',
        name: '首页',
        component: () => import('@/views/index/index'),
        meta: { title: '首页', icon: 'index' }
      }
    ]
  }
  //  404重定向先注释,稍后步骤里做说明
  // { path: '*', redirect: '/404', hidden: true }
]

asyncRoutes: 代表那些需求动态判断权限并通过 addRoutes 动态添加的页面,现通过模拟接口获取。

模拟接口mock/router.js中需要包含的数据格式如下:

const data = {
  router: [{
    path: '/goodsInfo',
    component: 'Layout',
    redirect: '/goodsInfo',
    name: 'TAB1',
    meta: { title: 'TAB1', icon: 'el-icon-s-help' },
    children: [
      {
        path: 'goodsInfo',
        name: 'TAB1-1',
        component: '/goodsInfo/index',
        meta: { title: 'TAB1', icon: 'table', button: ['add'] }
      }
    ]
  },
  {
    path: '/installInfo',
    component: 'Layout',
    redirect: '/installInfo/installRrecord',
    name: 'TAB2',
    meta: { title: 'TAB2', icon: 'el-icon-s-help' },
    children: [
      {
        path: 'installRrecord',
        name: 'TAB2-1',
        component: '/installInfo/installRecord',
        meta: { title: 'TAB2-1', icon: 'table', button: ['add'] }
      },
      {
        path: 'summaryStatistics',
        name: 'TAB2-2',
        component: '/installInfo/summaryStatistics',
        meta: { title: 'TAB2-2', icon: 'tree', button: ['add', 'delete'] }
      }
    ]
  },
]
}

module.exports = [{
  url: '/vue-admin-template/router/list',
  type: 'get',
  response: config => {
    const items = data
    return {
      code: 20000,
      data: {
        router: items
      }
    }
  }
}]

第二步:在登录后获取用户权限进行路由请求

 在前端权限控制(一):前端权限管理及动态路由配置方案中,与页面级权限控制里的方案二相同,登录login和返回用户路由信息get_router两件事分开请求。

2.1:登录后存储用户role信息

store\modules\user.js中添加onePermission字段,

const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    onePermission:null
  }
}

 在login成功之后 ,存储用户权限

commit('SET_PERMISSION', data.onePermission) // 用户权限标识

2.2:根据onePermission获取权限路由列表

在store\modules\目录下创建permission.js,修改代码如下:

如后台传递格式不符合router规范(例如传递的不是树形结构数据),可自行在此文件中进行处理。

import { getRouterList } from '@/api/router'

const getDefaultState = () => {
  return {
    router: null,
    buttonList:[]
  }
}

const state = getDefaultState()

const mutations = {
  SET_ROUTER: (state, router) => {
    state.router = router
  },
  SET_BUTTON: (state, button) => {
    state.buttonList = button
  }
}

const actions = {
  // get user router
  getRouter({ commit, state }) {
    return new Promise((resolve, reject) => {
      getRouterList(state.onePermission).then(response => {
        const { data } = response
        if (!data) {
          return reject('get user router,路由获取失败, please Login again.')
        }
        const { router } = data.router
       
        commit('SET_ROUTER', router) //设置路由变量
        resolve(router)
      }).catch(error => {
        reject(error)
      })
    })
  }

}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

buttonList 设置为按钮级权限控制,具体实现见:

前端权限控制(三):根据后台接口数据传递页面按钮权限-实现按钮级权限控制

第三步:拦截/获取路由

找到与根目录同级的permission.js,修改代码如下:

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'

// 获取组件、拼接路径的方法
const _import = require('@/router/_import_' + process.env.NODE_ENV) 
// 引入架构组件Layout 
import Layout from '@/layout'
 
NProgress.configure({ showSpinner: false }) // 进度条配置

const whiteList = ['/login'] // 没有重定向白名单路由


router.beforeEach(async(to, from, next) => {
  // 开始进度条
  NProgress.start()

  // 设置页面标题
  document.title = getPageTitle(to.meta.title)

  // 判断用户是否已经登录
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // 如果已经登录,则重定向到主页
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserRouter = store.getters.router // 是否有路由表
      if (hasGetUserRouter) {
         // 设置当前页面按钮权限
        const button = to.meta.button
        store.commit('permission/SET_BUTTON', button)
        next()
      } else {
        try {
          // 获取路由表
          const list = await store.dispatch('permission/getRouter')

          routerGo(to, next, list)  
          // next()
        } catch (error) {
          await store.dispatch('user/resetToken')
          
          Message({ type:'error', message: error || 'Has Error' })
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }

    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar 进度条
  NProgress.done()
})


// 导航到一个新的路由方法封装
function routerGo(to, next, routerList) {
  const newRouter = filterAsyncRouter(routerList)
  newRouter.push({ path: '*', redirect: '/404', hidden: true }) // 404放在最后
  // 必须在addroutes前,使用router.options.routes=XXXXX的方法手动添加,才会显示菜单
  router.options.routes = router.options.routes.concat(newRouter);
  // router.options.routes = newRouter;  

  router.addRoutes(newRouter) // 动态添加路由
  next({ ...to, replace: true })
}

// 过滤路由方法封装-拼接路径
function filterAsyncRouter(asyncRouterMap) { // 遍历后台传来的路由字符串,转换为组件对象
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') { // Layout组件特殊处理
        route.component = Layout
      } else {
        route.component = _import(route.component)
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    }
    return true
  })
  return accessedRouters
}

判断登录后,通过store.getters.router判断是否存有路由表,是的话获取当前页面按钮权限后直接next()进行跳转;否的话用axios后台取一次路由数据并存到vuex。

要点

  1. 因为后台传回的组件路径‘component’是字符串,所以要把加载组件的过程 封装成一个方法,因为有多级路由的出现,所以要写成遍历递归方法,确保把每个component转成对象。
    要注意的是Layout组件需要特殊处理。Layout 是架构组件,不在后台返回,在文件里单独引入:将后端传回的"component": "Layout", 转为"component": Layout组件对象处理。

    在router文件夹下新建:_import_development.js_import_production.js文件。

    // router/_import_production.js
    module.exports = file => () => import('@/views/' + file + '.vue')
    
    
    // router/_import_development.js
    module.exports = file => require('@/views/' + file + '.vue').default  // vue-loader at least v13.0.0+
    
  2. 404 页面一定要最后加载,如果放在constantRouterMap一同声明了404,后面的所以页面都会被拦截到404,详细的问题见:addRoutes when you've got a wildcard route for 404s does not work

  3.  因为vue-admin-template侧边栏菜单是取的router.options.routes数据,所以必须在addroutes前,使用router.options.routes=XXXXX的方法手动添加,才会显示菜单。

  4. addRoutes()之后通过next()第一次访问被添加的路由可能出现白屏的情况,也不会报错。大致理解为, 当进入 路由的  前置钩子 (router.beforEach) 的时候, 路由的结构是不会发生变化的, 至少本次跳转, 路由的结构是不会变化的。此时就要使用next({ ...to, replace: true })重定向当前的路由来确保addRoutes()时动态添加的路由已经被完全加载上去。

=================================================================

完成:

  

实现代码如下:

前端VUE权限管理(包含菜单权限和按钮权限),router.addRoutes根据后台接口传递数据生成动态路由-Javascript文档类资源-CSDN下载

Logo

前往低代码交流专区

更多推荐