使用Vue实现后台管理系统的动态路由以及侧边栏菜单

介绍及说明

1、背景介绍:最近因为公司的项目,对一个后台管理系统进行前端重构,原项目是SSM架构的前后端没有分离,前端用JSP和jQuery来写的,在完成第一期之后,我强烈要求前后端分离,并使用vue来组件化开发。所以经过几天的摸索完成了项目的整体搭建和基础建设。
2、项目要求:因为原先的项目的菜单都是后台来动态生成的,所以用vue来写也要从后台获取路由表,前端再处理成我们想要的路由格式,再完成侧边栏菜单;并且前端页面里有一个模块专门来管理生成菜单和权限(也就是前端自定义一些菜单信息传给后端,后端把数据存到数据库,并根据角色权限处理完数据再返回路由表给前端,前端就可以动态生成侧边栏菜单了)。

实现过程

首先本项目是基于vue-element-admin的后台管理模板( vue-element-admin),这个模板很多功能都有现成的,很方便,拉取下来之后就可以基于这个进行开发了,这个模板有很详细的文档说明( vue-element-admin使用文档),里面也有动态路由的集成方案,本项目选择的就是路由全部通过后端来生成,前端来自定义需要的路由。
步骤如下:
1、用户登录成功之后(登录的功能,模板里也写的很好,换成公司的登录接口就可以直接用),再根据用户名获取用户的权限,然后根据用户权限获取后端的路由数据:
1)权限处理和生成动态路由:src/permission.js文件

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // 进度条
import 'nprogress/nprogress.css' // 进度条样式
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
// import { getRoutes } from './api/role'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login', '/auth-redirect'] // 没有重定向白名单

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

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

// 确定用户是否已登录,获取token
const hasToken = getToken()

if (hasToken) {
  if (to.path === '/login') {
    // 如果已登录,则重定向到主页
    next({ path: '/' })
    NProgress.done()
  } else {
    // 确定用户是否通过getInfo获得了他的权限角色
    const hasRoles = store.getters.roles && store.getters.roles.length > 0
    if (hasRoles) {
      next()
    } else {
      try {
        // 获取用户权限信息
        const { roles } = await store.dispatch('user/getInfo')

        // 根据角色生成可访问路由映射
        const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
        // console.log(accessRoutes)

        // 动态添加可访问路由
        router.options.routes = router.options.routes.concat(accessRoutes)
        router.addRoutes(accessRoutes)
        // hack方法 以确保addRoutes是完整的
        next({ ...to, replace: true })
        // console.log(router)
      } catch (error) {
        // 删除令牌,进入登录页面重新登录
        await store.dispatch('user/resetToken')
        Message.error(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()
})	

2)获取用户权限的代码:src/store/modules/user.js

// 获取用户权限信息
getInfo({ commit, state }) {
  return new Promise((resolve, reject) => {
    getInfo(state.code).then(response => {
      const { data } = response
      // console.log(data)
      if (!data) {
        reject('验证失败,请重新登录.')
      }

      let roles = []
      roles = roles.concat(data.usrGroupcode)

      const name = data.userName

      // // 角色必须是非空数组
      if (!roles || roles.length <= 0) {
        reject('getInfo: 角色必须是非空数组!')
      }
      const avatar = data.userCode
      commit('SET_NAME', name)
      commit('SET_ROLES', roles)
      commit('SET_CODE', avatar)
      const users = {
        roles: roles,
        introduction: '',
        code: avatar,
        name: name
      }
      // console.log(users)
      resolve(users)
    }).catch(error => {
      reject(error)
    })
  })
},

2、处理后端返回的路由信息并存到vuex里:src/store/modules/permission.js

import { constantRoutes } from '@/router'
import { getMenu } from '../../api/role'
import Layout from '@/layout'

/**
 * 将后端content字段转成组件对象
 */
function _import(file) {
  return () => import(`@/views/${file}/index.vue`)
}

/**
 * 通过递归过滤异步路由表
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes) {
  const res = []
  // eslint-disable-next-line no-empty
  for (let i = 0; i < routes.length; i++) {
    res.push({
      path: routes[i].content === 'Layout' ? `/${routes[i].path}` : routes[i].path,
      component: routes[i].content === 'Layout' ? Layout : _import(routes[i].content),
      name: routes[i].path,
      meta: {
        title: routes[i].menuname,
        icon: routes[i].icon
      },
      children: routes[i].children && routes[i].children.length ? filterAsyncRoutes(routes[i].children) : []
    })
  }
  return res
}

const state = {
  routes: constantRoutes,
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise((resolve, reject) => {
      let accessedRoutes
      let asyncRoutes = []
      getMenu(roles[0]).then(res => {
        if (res.msg === 'SUCCESS') {
          asyncRoutes = res.data
        } else {
          asyncRoutes = []
        }
        // console.log(asyncRoutes)
        accessedRoutes = filterAsyncRoutes(asyncRoutes)
        // 最后添加
        const unfound = { path: '*', redirect: '/404', hidden: true }
        accessedRoutes.push(unfound)
        // console.log(accessedRoutes)
        // console.log('store:accessedRoutes')

        commit('SET_ROUTES', accessedRoutes)
        resolve(accessedRoutes)
      }).catch(error => {
        reject(error)
      })
    })
  }
}

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

基础路由和统一路由:src/router/index.js

export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path*',
        component: () => import('@/views/redirect/index')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/auth-redirect',
    component: () => import('@/views/login/auth-redirect'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error-page/401'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'home',
    meta: { title: '首页', affix: true },
    hidden: true,
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard'),
        hidden: true
      }
    ]
  }
 ]

3、然后再通过递归生成侧边栏菜单:src/layout
这个时候打印路由信息是可以得到正常的路由表信息;根据路由表生成侧边栏:
this.$router.options.routes 可以获取路由表信息,具体逻辑vue-element-admin模板里有现成的,这里就不解释了。

最后说一下后端返回的数据结构:

[{
applicationid: 1035
children: [ //子节点,结构和当前一样
  0:{
  	applicationid: 1035
  	children: [
  		0: {
  			applicationid: 1035
  			children: null
  			content: "system/system-management/menu"
  			formid: 101100
  			formtype: 1
  			icon: ""
  			inactived: 1
  			memo: "EAM"
  			menuid: 101100
  			menuname: "菜单管理"
  			parentmenuid: 101000
  			parentrowid: "01KBGO"
  			path: "menu"
  			roles: ""
  			rowid: "01KBGP"
  			seq: 101100
  			treecontrol: "01KBGP01KBGO01KBG9"
  		}
  	]
  	content: "system/system-management"
  	formid: 101000
  	formtype: 1
  	icon: ""
  	inactived: 1
  	memo: "EAM"
  	menuid: 101000
  	menuname: "系统设置"
  	parentmenuid: 100000
  	parentrowid: "01KBG9"
  	path: "system-management"
  	roles: ""
  	rowid: "01KBGO"
  	seq: 101000
  	treecontrol: "01KBGO01KBG9"
  }
]  
content: "Layout"  //对应组件的名称
formid: 100000
formtype: 1
icon: ""  //菜单的图标
inactived: 1  //是否启用
memo: "EAM"
menuid: 100000  //菜单的id
menuname: "系统管理"  //菜单的名称
parentmenuid: null  //父级菜单id
parentrowid: ""
path: "system"  //路由
roles: ""
rowid: "01KBG9"
seq: 100000  //排序
treecontrol: "01KBG9"
}]

猜你喜欢

转载自blog.csdn.net/weixin_44939142/article/details/107743946