主要思路如下:
- 用户登录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] ] } }
至此已完成,亲测可行,第一次登陆会出现找不到路由,再次登陆后正常,待解决。