赞
踩

1、前端定义静态路由(login登录页这种不需要权限的默认路由)
2、用户登陆时调接口获取用户信息,然后登录到首页
3、前后端定义好路由返回的格式
4、在路由导航钩子beforeEach中去调接口获取动态路由,递归处理该数据为前端可用的路由数据,使用router.addRouters()添加路由
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 localStorage reducer(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
路由钩子逻辑:
是否为白名单:
是: 直接进入
不是:判断是否有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].routes const asyncRouter = filterASyncRoutes(res) // 递归处理后台返回的路由 store.commit('routerList/setRouterList', asyncRouter) // 存储到异步的路由到vuex store.commit('routerList/setHasRoutes', true) // 设置登录状态为true router.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 bar NProgress.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].routes
const asyncRouter = filterASyncRoutes(res) // 递归处理后台返回的路由
store.commit('routerList/setRouterList', asyncRouter) // 存储到异步的路由到vuex
store.commit('routerList/setHasRoutes', true) // 设置登录状态为true
router.addRoutes(asyncRouter) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 router.addRoutes之后的next()可能会失效,可能next()的时候路由并没有完全add完成 通过next(to)解决
}, 500)
}
我公司目前就是采用后端返回路由做的权限管理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。