实现思路
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 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
步骤四:路由导航钩子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].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)
}
我公司目前就是采用后端返回路由做的权限管理
希望能帮到你哦
参考代码在github 给我来个star