实现思路
1、前端定义静态路由(login登录页这种不需要权限的默认路由)
2、用户登陆时调接口获取用户信息,然后登录到首页
3、前后端定义好路由返回的格式
4、在路由导航钩子beforeEach中去调接口获取动态路由,递归处理该数据为前端可用的路由数据,使用router.addRouters()添加路由
步骤一:前端定义静态路由(login登录页这种不需要权限的默认路由)
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)// 解决重复点击路由报错的BUG
// 下面这段代码主要解决这个问题 :Uncaught (in promise) Error: Redirected when going from "/login" to "/index" via a navigation guard.
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {return originalPush.call(this, location).catch(err => err)
}// 定义好静态路由
const routes = [{path: '/login',name: 'login',component: () => import('../views/login'),hidden: true,},
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes,
})export default router
步骤二:用户登陆时调接口获取用户信息,然后登录到首页
login/index.vue
methods: {login () {this.$refs.userForm.validate((valid) => {if (valid) {// 模拟登录接口去请求用户数据setTimeout(() => {// 这里的res就是模拟后台返回的用户数据const res = dynamicUserData.filter((item) => item.username === this.user.username)[0]console.log(res)// 存储用户的信息及token到vuex,并做sessionStorage持久化处理this.$store.commit('User/saveUserInfo',res)Message({ type: 'success', message: "登录成功", showClose: true, duration: 3000 })this.$router.push({ path: "/index" })}, 1000)} else return false})}}
附:vuex持久化处理:使用vuex-persistedstate
插件将User仓库的内容存储到sessionStorage中
import Vue from 'vue'
import Vuex from 'vuex'
import User from './modules/user'
import permission from './modules/permission'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {User,permission,},plugins: [createPersistedState({storage: window.sessionStorage, // 可选sessionStorage localStoragereducer(val) {return {User: val.User,}},}),],
})
步骤三:前后端定义好路由返回的格式
// 后台返回的数据结构
const dynamicUser = [{name: '管理员',avatar: 'https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image',desc: '管理员 - admin',username: 'admin',password: '654321',token: 'rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f',routes: [{id: 1,name: '/',path: '/',component: 'Layout',redirect: '/index',hidden: false,children: [{ name: 'index', path: '/index', meta: { title: 'index' }, component: 'index/index' }],},{id: 2,name: '/form',path: '/form',component: 'Layout',redirect: '/form/index',hidden: false,children: [{ name: '/form/index', path: '/form/index', meta: { title: 'form' }, component: 'form/index' }],},{id: 3,name: '/example',path: '/example',component: 'Layout',redirect: '/example/tree',meta: { title: 'example' },hidden: false,children: [{ name: '/tree', path: '/example/tree', meta: { title: 'tree' }, component: 'tree/index' },{ name: '/copy', path: '/example/copy', meta: { title: 'copy' }, component: 'tree/copy' },],},{id: 4,name: '/table',path: '/table',component: 'Layout',redirect: '/table/index',hidden: false,children: [{ name: '/table/index', path: '/table/index', meta: { title: 'table' }, component: 'table/index' }],},{id: 5,name: '/admin',path: '/admin',component: 'Layout',redirect: '/admin/index',hidden: false,children: [{ name: '/admin/index', path: '/admin/index', meta: { title: 'admin' }, component: 'admin/index' }],},{id: 6,name: '/people',path: '/people',component: 'Layout',redirect: '/people/index',hidden: false,children: [{ name: '/people/index', path: '/people/index', meta: { title: 'people' }, component: 'people/index' }],},],},{name: '普通用户',avatar: 'https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/6364348965908f03e6a2dd188816e927~300x300.image',desc: '普通用户 - people',username: 'people',password: '123456',token: '4es8eyDwznXrCX3b3439EmTFnIkrBYWh',routes: [{id: 1,name: '/',path: '/',component: 'Layout',redirect: '/index',hidden: false,children: [{ name: 'index', path: '/index', meta: { title: 'index' }, component: 'index/index' }],},{id: 2,name: '/form',path: '/form',component: 'Layout',redirect: '/form/index',hidden: false,children: [{ name: '/form/index', path: '/form/index', meta: { title: 'form' }, component: 'form/index' }],},{id: 3,name: '/example',path: '/example',component: 'Layout',redirect: '/example/tree',meta: { title: 'example' },hidden: false,children: [{ name: '/tree', path: '/example/tree', meta: { title: 'tree' }, component: 'tree/index' },{ name: '/copy', path: '/example/copy', meta: { title: 'copy' }, component: 'tree/copy' },],},{id: 4,name: '/table',path: '/table',component: 'Layout',redirect: '/table/index',hidden: false,children: [{ name: '/table/index', path: '/table/index', meta: { title: 'table' }, component: 'table/index' }],},{id: 6,name: '/people',path: '/people',component: 'Layout',redirect: '/people/index',hidden: false,children: [{ name: '/people/index', path: '/people/index', meta: { title: 'people' }, component: 'people/index' }],},],},
]export default dynamicUser
步骤四:路由导航钩子beforeEach处理
路由钩子逻辑:
是否为白名单:是: 直接进入不是:判断是否有token无token:跳转到login登录页有token:判断用户路由状态(是否有路由)有路由: 直接进入无路由: 调接口获取动态路由递归处理返回的路由将递归处理好的路由存储到vuex,设置用户路由状态为true使用router.addRouters()添加路由
路由导航守卫:
import router from './index'
import Layout from '../layout/index'
import NProgress from 'nprogress' // progress bar
import store from '@/store'
import menu from '@/mock/menu.js'// 路由拼接
function loadView(view) {return () => import(`@/views/${view}`)
}
// 路由过滤 遍历路由 转换为组件对象和路径
function filterASyncRoutes(data) {// console.log(data)const routes = data.filter(item => {if (item['component'] === 'Layout') {item.component = Layout} else {item['component'] = loadView(item['component'])}// 路由递归,转换组件对象和路径if (item['children'] && item['children'].length > 0) {item['children'] = filterASyncRoutes(item.children)}return true})// 排序routes.sort((a, b) => a['id'] - b['id'])return routes
}NProgress.configure({ showSpinner: false }) // NProgress Configuration// 白名单页面直接进入
const whiteList = ['/login']router.beforeEach((to, from, next) => {NProgress.start()// 白名单页面,不管是否有token,是否登录都直接进入if (whiteList.indexOf(to.path) !== -1) {next()return false}// 有token(代表了有用户信息,但是不确定有没有路由信息)if (store.state.User.token) {// 判断当前用户是否是登录状态, 是登录状态则一定有路由,直接放行,不是登录状态则去获取路由菜单登录// 刷新时store.state.routerList.hasRoutes会重置为false,重新去获取 异步路由if (!store.state.routerList.hasRoutes) {setTimeout(() => {const res = menu.filter(item => item.token === store.state.User.token)[0].routesconst asyncRouter = filterASyncRoutes(res) // 递归处理后台返回的路由store.commit('routerList/setRouterList', asyncRouter) // 存储到异步的路由到vuexstore.commit('routerList/setHasRoutes', true) // 设置登录状态为truerouter.addRoutes(asyncRouter) // 动态添加可访问路由表next({ ...to, replace: true }) // hack方法 router.addRoutes之后的next()可能会失效,可能next()的时候路由并没有完全add完成 通过next(to)解决}, 500)} else {next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面}}else {next({path:'/login'})}
})router.afterEach(() => {// finish progress barNProgress.done()
})
注意
这里将异步路由和路由状态的数据放到了routerList.js里面,但是这里是没做缓存的
原因:让用户刷新时这里的数据重置,然后重新走路由钩子的时候就会去调接口获取路由信息。
(为什么不把路由放到sessionStorage中? 原因是路由数组中的component无法保存到sessionStorage中,() => import(@/views/${userAuth.component}) 这种保存不到sessionStorage中 )
获取的用户信息需要存储在vuex并做持久化处理,但是获取的菜单存在vuex中不做持久化处理,让用户在刷新时会清空,重新走获取菜单的接口,也就是下面这段代码
if (!store.state.routerList.hasRoutes) {setTimeout(() => {const res = menu.filter(item => item.token === store.state.User.token)[0].routesconst asyncRouter = filterASyncRoutes(res) // 递归处理后台返回的路由store.commit('routerList/setRouterList', asyncRouter) // 存储到异步的路由到vuexstore.commit('routerList/setHasRoutes', true) // 设置登录状态为truerouter.addRoutes(asyncRouter) // 动态添加可访问路由表next({ ...to, replace: true }) // hack方法 router.addRoutes之后的next()可能会失效,可能next()的时候路由并没有完全add完成 通过next(to)解决}, 500)
}
我公司目前就是采用后端返回路由做的权限管理
希望能帮到你哦
参考代码在github 给我来个star