vue-element-template实战(五) 获取后端路由表动态生成权限

主要思路如下:

  • 用户登录login获取token
  • 拿着token请求用户信息,同时后端返回一个路由表
  • 前端解析后动态添加路由表,同时存储到本地localstorage
  • 刷新页面或者退出登录或者登录过期等时,会进行相应的判断,重新渲染路由

1、在src/router文件夹下新建_import.js,用于匹配组件,代码如下:

export default file => {
    return map[file] || null
  }
  const map = {
    'Layout': () => import('@/layout'),
    'table': () => import('@/views/table/index'),
    'tree': () => import('@/views/tree/index'),
    'form': () => import('@/views/form/index'),
    'menu1': () => import('@/views/nested/menu1/index'),
    'menu1-1': () => import('@/views/nested/menu1/menu1-1'),
    'menu1-2': () => import('@/views/nested/menu1/menu1-2'),
    'menu1-2-1': () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
    'menu1-2-2': () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
    'menu1-3': () => import('@/views/nested/menu1/menu1-3'),
    'menu2': () => import('@/views/nested/menu2/index')
  }
  

 2、在src/utils文件夹下新建addRouter.js,用于解析后端返回的路由,具体解析方法和数据形式还是要看项目具体来分析,代码如下:

import _import from '../router/_import'// 获取组件的方法

function addRouter(routerlist) {
  routerlist.forEach(e => {
    // 删除无用属性
    delete e.id
    e.component = _import(e.component) // 动态匹配组件
    if (e.redirect === '') {
      delete e.redirect
    }
    if (e.name === '') {
      delete e.name
    }
    if (e.icon !== '' && e.title !== '') { // 配置 菜单标题 与 图标
      e.meta = {
        title: e.title,
        icon: e.icon
      }
    } else if (e.icon === '' && e.title !== '') {
      e.meta = {
        title: e.title
      }
    }
    delete e.icon
    delete e.title
    if (e.children != null) {
      // 存在子路由就递归
      addRouter(e.children)
    }
  })
  // console.log(routerlist)
  return routerlist
}
export { addRouter }

3、修改src/permission.js,动态添加路由

  3.1 导入addRouter

import { addRouter } from './utils/addRouter'

  3.2 动态添加路由函数,注意404页面要在这最后添加到路由表中,不能放在router默认的路由中,否则刷新页面就会跳转到404

function gotoRouter(to, next) {
  try {
    getRouter = addRouter(getRouter) // 解析路由
    const newRouters = router.options.routes.concat(getRouter) // 连接获取到的路由
    newRouters.push({ path: '*', redirect: '/404', hidden: true }) // 最后添加404页面
    console.log('路由:', newRouters)
    // router.options.routes = newRouters
    router.addRoutes(newRouters) // 动态添加路由
    store.commit('SET_ROUTER', newRouters) // 将路由数据传递给VUEX,做侧边栏菜单渲染工作
    next({ to, replace: true })
  } catch (error) {
    localStorage.setItem('login_static', 0)
    store.dispatch('user/resetToken')
    Message.error(error || 'Has Error')
    next(`/login?redirect=${to.path}`)
    NProgress.done()
  }
}

  3.3 请求路由数据函数以及从本地获取路由表函数

function setRouterList(to, next) {
  store.dispatch('user/getInfo').then(data => { // 请求路由数据
    localStorage.setItem('router', JSON.stringify(data.routers))
    getRouter = getRouterList('router') // 拿到路由
    gotoRouter(to, next)
  })
}

  3.4 修改beforeEach

var getRouter
router.beforeEach(async(to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()
  const hasLogin = getRouterList('login_static')
  console.log('hasToken:', hasToken) // 是否已获取token
  console.log('hasLogin:', hasLogin) // 登录状态
  if (hasToken && hasLogin === 1) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo')

          next()
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
      if (!getRouter) {
        console.log('路由信息不存在')
        const hasRouterData = getRouterList('router')
        if (hasRouterData) {
          console.log('路由信息存在 说明已经请求到路由 直接解析路由信息')
          getRouter = hasRouterData
          await gotoRouter(to, next)
        } else {
          console.log('localStorage不存在路由信息 需要重新请求路由信息 并解析路由')
          setRouterList(to, next)
        }
      } else {
        console.log('路由信息存在 直接通过')
        next()
      }
    }
  } 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()
    }
  }
})

 

4、把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: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  }
]

5、修改store文件夹中index.js,添加路由表状态和获取路由表的方法

const store = new Vuex.Store({
  state: {
    routerList: [] // 用于存储路由表
  },
  mutations: {
    // 用于获取路由表
    SET_ROUTER(state, routerList) {
      state.routerList = routerList
    }
  },
  modules: {
    app,
    settings,
    user
  },
  getters
})

6、修改store/modules中的user.js,在login时保存登录状态,在logout时清除登录状态以及本地保存的路由表

// user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        localStorage.setItem('login_static', 1) // 保存登录状态
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
// user logout
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        removeToken() // must remove  token  first
        localStorage.removeItem('router') // 删除本地存储的路由表
        localStorage.setItem('login_static', 0) // 切换登录状态
        resetRouter()
        commit('RESET_STATE')
        location.reload() // 刷新页面 以重置getRouter
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  // remove token
  resetToken({ commit }) {
    return new Promise(resolve => {
      removeToken() // must remove  token  first
      localStorage.removeItem('router') // 删除本地存储的路由表
      localStorage.setItem('login_static', 0) // 切换登录状态
      commit('RESET_STATE')
      resolve()
    })
  }

7、修改layout/component/Sliebar中的index.vue

routes() {
      // return this.$router.options.routes
      return this.$store.state.routerList // 将VUEX中的路由表挂载
    },

8、使用mock模拟下后端返回的路由信息,在getInfo中与用户信息一起返回的

// 模拟路由表
const routerTable = [
  {
    'id': 1,
    'name': 'Example',
    'path': '/example',
    'component': 'Layout',
    'redirect': '/example/table',
    'title': 'Example',
    'icon': 'example',
    'children': [{
      'id': 2,
      'name': 'Table',
      'path': '/example/table',
      'component': 'table',
      'title': 'Table',
      'icon': 'table'
    },
    {
      'id': 3,
      'name': 'Tree',
      'path': '/example/tree',
      'component': 'tree',
      'title': 'Tree',
      'icon': 'tree'
    }]
  },
  {
    'id': 4,
    'path': '/form',
    'component': 'Layout',
    'children': [{
      'id': 5,
      'name': 'Form',
      'path': '/form/index',
      'component': 'form',
      'title': 'Form',
      'icon': 'form'
    }]
  },
  {
    'id': 6,
    'name': 'Nested',
    'path': '/nested',
    'component': 'Layout',
    'redirect': '/nested/menu1/index',
    'title': 'Nested',
    'icon': 'nested',
    'children': [{
      'id': 7,
      'name': 'Menu1',
      'path': '/nested/menu1/index',
      'component': 'menu1',
      'title': 'Menu1',
      'children': [{
        'id': 8,
        'name': 'Menu1-1',
        'path': '/nested/menu1/menu1-1',
        'component': 'menu1-1',
        'title': 'Menu1-1'
      },
      {
        'id': 9,
        'name': 'Menu1-2',
        'path': '/nested/menu1/menu1-2',
        'component': 'menu1-2',
        'title': 'Menu1-2',
        'children': [{
          'id': 10,
          'name': 'Menu1-2-1',
          'path': '/nested/menu1/menu1-2/menu1-2-1',
          'component': 'menu1-2-1',
          'title': 'Menu1-2-1'
        },
        {
          'id': 11,
          'name': 'Menu1-2-2',
          'path': '/nested/menu1/menu1-2/menu1-2-2',
          'component': 'menu1-2-2',
          'title': 'Menu1-2-2'
        }]
      },
      {
        'id': 12,
        'name': 'Menu1-3',
        'path': '/nested/menu1/menu1-3',
        'component': 'menu1-3',
        'title': 'Menu1-3'
      }]
    },
    {
      'id': 13,
      'name': 'Menu2',
      'path': '/nested/menu2/index',
      'component': 'menu2',
      'title': 'Menu2'
    }],
  },
  {
    'id': 14,
    'path': 'external-link',
    'component': 'Layout',
    'children': [{
      'path': 'https://panjiachen.github.io/vue-element-admin-site/#/',
      'title': 'External Link',
      'icon': 'link'
    }]
  }
]
const users = {
  'admin-token': {
    roles: ['admin'],
    introduction: 'I am a super administrator',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Super Admin',
    routers: routerTable
  },
  'editor-token': {
    roles: ['editor'],
    introduction: 'I am an editor',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Normal Editor',
    routers: [
      routerTable[0]
    ]
  }
}

至此已完成,亲测可行,第一次登陆会出现找不到路由,再次登陆后正常,待解决。

posted @ 2020-06-26 15:59  世界因你而小  阅读(2757)  评论(1编辑  收藏  举报