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]
    ]
  }
}

至此已完成,親測可行,第一次登陸會出現找不到路由,再次登陸后正常,待解決。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM