vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue根据角色添加路由

Vue如何根据角色获取菜单动态添加路由

作者:劫辞

这篇文章主要介绍了Vue如何根据角色获取菜单动态添加路由,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

如果大家写过后台管理系统的项目,那么动态路由一定是绕不开的,如果想偷懒的话,就把所有路由一开始都配置好,然后只根据后端返回的菜单列表渲染就好菜单就好了,但是这样的隐患就是我在地址栏输入的地址的时候,也会进入这个页面,不偷懒的方法就是本文要介绍的,真动态路由了,当然不会仅仅只是介绍使用数据怎么换成动态路由添加就好了,会从登录获取token后请求菜单列表…最后注册完成,这一系列流程完整的实现一次,相信对于第一次接触这个案例的朋友会有帮助

前提提要

需求分析

具体实现

配置静态路由

根据上面的粗略的分析,第一步就是创建路由,这一步非常简单,我直接粘贴代码了,如下:

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
	mode: 'hash',
	routes: []
})
export default router

这就是一个最基础的结构了,而在这个需求中,至少有两个路由一定是静态的,一个是 login,一个是 layout,当然通常还有个一个任意路由,表示 404,这里我就不写了,大家有时间自己添加一下就好,如下:

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
	mode: 'hash',
	routes: [
		{
			path: '/',
			name: 'layout',
			component: () => import('@/layout')
		},
		{
			path: '/login',
			name: 'login',
			component: () => import('@/views/login')
		}
	]
})
export default router

添加两个静态路由非常简单吧,然后把这个在 main js 页面引入使用,我就不展示了

路由权限判断

这只是一个简单的路由权限判断,具体的还需要根据业务来扩展,根据这个关系图,我们可以写出如下代码:

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import store from '@/store'
const router = new VueRouter({
	mode: 'hash',
	routes: [
		{
			path: '/',
			name: 'layout',
			component: () => import('@/layout')
		},
		{
			path: '/login',
			name: 'login',
			component: () => import('@/views/login')
		}
	]
})
router.beforeEach((to, from, next) => {
	const token = store.state.login.token
	if (token) {
		if (to.path === '/login') {
			next(false)
		} else {
			next()
		}
	} else {
		if (to.path === '/login') {
			next()
		} else {
			next('/login')
		}
	}
})
export default router

登录

实现这点的方法也不止一种,本文采用的是在 store 的 login 模块中完成登录,至于 axios 的封装或者基于 xhr 等等的请求方面,我这里不做解析了

store 的基础配置不做赘述了,直接粘贴代码,如下:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import login from './login'
const store = new Vuex.Store({
	modules: { login }
})
export default store

至于 login 模块的话,书写也非常简单,编写登录函数,登录成功之后同步获取菜单数据,如下:

import { loginApi, menuApi } from '@/api'
import router from '@/router'
export default {
	namespaced: true,
	state: {
		userInfo: {} || localStorage.getItem('user_info'),
		token: '' || localStorage.getItem('token'),
		menuList: [] || localStorage.getItem('menu_list')
	},
	mutations: {
		SET_MENU_LIST(state, payload) {
			state.menuList = payload
		},
		SET_USER_INFO(state, payload) {
			state.userInfo = payload
			localStorage.setItem('user_info', JSON.stringify(payload))
		},
		SET_TOKEN(state, payload) {
			state.token = payload
			localStorage.setItem('token', payload)
		},
        // 退出登录
		LOG_OUT() {
			localStorage.removeItem('token')
			localStorage.removeItem('user_info')
			localStorage.removeItem('menu_list')
			// 刷新页面-因为路由权限的存在会导航到login,并且通过这个刷新可以避免重复添加路由
			window.location.reload()
		}
	},
	actions: {
		async login({ commit }, payload) {
            // 登录请求-获取token
			const loginResp = await loginApi.reqLogin(payload)
			if (loginResp?.errorCode !== 0) return
			commit('SET_USER_INFO', loginResp.data.userInfo)
			commit('SET_TOKEN', loginResp.data.token)
			// 请求菜单列表
			const menuListResp = await menuApi.reqGetMenuList()
			localStorage.setItem('menu_list', JSON.stringify(menuListResp.data))
			commit('SET_MENU_LIST', menuListResp.data)
			// 跳转至首页
			router.push('/home')
		}
	}
}

这部分代码还是非常简单的,在入口文件main.js 引用 store 和在登录界面收集表单数据提交调用这个 login 方法登录,大家就自己实现一下吧

现在我们获取到这个数据之后,表示我们可以完成两件事情,第一就是渲染侧边的菜单列表,第二就是根据这个来添加正确的动态路由

渲染菜单列表没有什么好说的,如果没有菜单栏的递归需求的话,菜单栏直接 cv 组件库的代码即可,需要递归的话就要自己封装一下了

添加动态路由

要添加动态路由,需要有两个数据,一个是远程获取的菜单数据,一个是前端的映射的组件关系。远程数据已经有了,前端映射的组件关系,就看你自己的业务来配置了,还是非常简单的,把你前端需要展示的页面都在一个 js 文件引入就好了,如下:

export default [
	{
		name: 'home',
		component: () => import('@/views/home')
	},
	{
		name: 'my',
		component: () => import('@/views/my')
	},
	{
		name: 'device-add',
		component: () => import('@/views/device/add')
	},
	{
		name: 'device-list',
		component: () => import('@/views/device/list')
	},
	{
		name: 'user-add',
		component: () => import('@/views/user/add')
	},
	{
		name: 'user-list',
		component: () => import('@/views/user/list')
	}
]

具体需要多少配置项,就视个人业务而定,我这里使用 name 匹配,你也可以是 path 或者其他属性

在看一下远程的数据具体是什么样的,有助于理解,如下:

[
    {
        "id": 1,
        "name": "home",
        "path": "/home",
        "nickname": "首页",
        "type": 2,
        "order": 1,
        "parentId": 0,
        "icon": "icon-tubiao_shouye-",
        "children": null
    },
    {
        "id": 2,
        "name": "device",
        "path": "/device",
        "nickname": "设备管理",
        "type": 1,
        "order": 2,
        "parentId": 0,
        "icon": "icon-guanli",
        "children": [
            {
                "id": 3,
                "name": "device-list",
                "path": "/device/list",
                "nickname": "设备列表",
                "type": 2,
                "order": 1,
                "parentId": 2,
                "icon": "icon-xuanzeweixuanze",
                "children": null
            },
            {
                "id": 4,
                "name": "device-add",
                "path": "/device/add",
                "nickname": "设备添加",
                "type": 2,
                "order": 2,
                "parentId": 2,
                "icon": "icon-xuanzeweixuanze",
                "children": null
            }
        ]
    },
    {
        "id": 5,
        "name": "my",
        "path": "/my",
        "nickname": "个人中心",
        "type": 2,
        "order": 3,
        "parentId": 0,
        "icon": "icon-xiazai",
        "children": null
    },
    {
        "id": 6,
        "name": "user",
        "path": "/user",
        "nickname": "用户管理",
        "type": 1,
        "order": 4,
        "parentId": 0,
        "icon": "icon-yonghuguanli",
        "children": [
            {
                "id": 7,
                "name": "user-list",
                "path": "/user-list",
                "nickname": "用户列表",
                "type": 2,
                "order": 1,
                "parentId": 6,
                "icon": "icon-xuanzeweixuanze",
                "children": null
            },
            {
                "id": 8,
                "name": "user-add",
                "path": "/user-add",
                "nickname": "用户添加",
                "type": 2,
                "order": 2,
                "parentId": 6,
                "icon": "icon-xuanzeweixuanze",
                "children": null
            }
        ]
    }
]

剩下的就是递归遍历的找出组装出对应的 route 配置的事情了,那么我们需要有这样的一个函数,来帮助我们完成这件事情,代码如下:

import router from '@/router'
// 前端映射的组件关系配置
import routeConfig from '@/router/route-config'
export default function (menuList) {
	const routeList = []
	const deepMenu = menuList => {
		for (const menu of menuList) {
			if (menu.children && menu.children.length > 0) {
				deepMenu(menu.children)
			} else {
				const item = routeConfig.find(item => item.name === menu.name)
				if (!item) return
				// 去掉第一项斜杠-子路由 path 属性不需要携带开头的 /
				const path = menu.path.replace(/^\//, '')
                // 路由元信息可以帮助我们完成一些其他操作的时候,需要的一些辅助数据
				routeList.push({ ...item, path, meta: { title: menu.nickname } })
			}
		}
	}
	deepMenu(menuList)
	for (const route of routeList) {
        // 遍历添加路由
		router.addRoute('layout', route)
	}
}

有了这个方法之后,自然就是使用,如下:

import { loginApi, menuApi } from '@/api'
import router from '@/router'
import menuToRoute from '@/utils/menu-to-route'
export default {
	namespaced: true,
	state: {
		userInfo: {} || localStorage.getItem('user_info'),
		token: '' || localStorage.getItem('token'),
		menuList: [] || localStorage.getItem('menu_list')
	},
	mutations: {
		SET_MENU_LIST(state, payload) {
			state.menuList = payload
            // 调用菜单转路由方法
			menuToRoute(payload)
		},
		SET_USER_INFO(state, payload) {
			state.userInfo = payload
			localStorage.setItem('user_info', JSON.stringify(payload))
		},
		SET_TOKEN(state, payload) {
			state.token = payload
			localStorage.setItem('token', payload)
		},
        // 退出登录
		LOG_OUT() {
			localStorage.removeItem('token')
			localStorage.removeItem('user_info')
			localStorage.removeItem('menu_list')
			// 刷新页面-因为路由权限的存在会导航到login,并且通过这个刷新可以避免重复添加路由
			window.location.reload()
		}
	},
	actions: {
		async login({ commit }, payload) {
			const loginResp = await loginApi.reqLogin(payload)
			if (loginResp?.errorCode !== 0) return
			commit('SET_USER_INFO', loginResp.data.userInfo)
			commit('SET_TOKEN', loginResp.data.token)
			// 请求菜单列表
			const menuListResp = await menuApi.reqGetMenuList()
			localStorage.setItem('menu_list', JSON.stringify(menuListResp.data))
			commit('SET_MENU_LIST', menuListResp.data)
			// 跳转至首页
			router.push('/home')
		}
	}
}

此时我们已经完成了整个效果的实现,当然还有一个问题,但是这个问题后面再说,先看一下效果,如图:

可以看到,不同的账户登录会因为角色不同展现的菜单也不同

修复刷新路由丢失问题

现在我们这个看着没什么问题,是因为我们没有点击刷新,先看看问题,如图:

一旦刷新之后就会导致动态路由清空,但是又没有重新注册添加,自然就会找不到这个路由了,因此白屏就很正常了

解决也非常简单,在每次刷新的时候,都在重新注册一次动态路由就好了,所以在 store 的 login 模块多添加一个方法,如下:

import { loginApi, menuApi } from '@/api'
import router from '@/router'
import menuToRoute from '@/utils/menu-to-route'
export default {
	namespaced: true,
	state: {
		userInfo: {} || localStorage.getItem('user_info'),
		token: '' || localStorage.getItem('token'),
		menuList: [] || localStorage.getItem('menu_list')
	},
	mutations: {
		SET_MENU_LIST(state, payload) {
			state.menuList = payload
			menuToRoute(payload)
		},
		SET_USER_INFO(state, payload) {
			state.userInfo = payload
			localStorage.setItem('user_info', JSON.stringify(payload))
		},
		SET_TOKEN(state, payload) {
			state.token = payload
			localStorage.setItem('token', payload)
		},
		LOG_OUT() {
			localStorage.removeItem('token')
			localStorage.removeItem('user_info')
			localStorage.removeItem('menu_list')
			window.location.reload()
		}
	},
	actions: {
		async login({ commit }, payload) {
			const loginResp = await loginApi.reqLogin(payload)
			if (loginResp?.errorCode !== 0) return
			commit('SET_USER_INFO', loginResp.data.userInfo)
			commit('SET_TOKEN', loginResp.data.token)
			const menuListResp = await menuApi.reqGetMenuList()
			localStorage.setItem('menu_list', JSON.stringify(menuListResp.data))
			commit('SET_MENU_LIST', menuListResp.data)
			router.push('/home')
		},
		// 加载本地数据
		async loadLocal({ commit }) {
			const menuList =  localStorage.getItem('menu_list')
			if (menuList) {
				commit('SET_MENU_LIST', JSON.parse(menuList))
			}
		}
	}
}

loadLocal 这个方法还可以初始化一下其他你需要初始化的信息,包括但不限于这个菜单列表,其他是导出这个方法,让其他人使用,可以直接从这个模块使用,也可以其他地方导出,我这里就在 store/index.js 文件下导出,如下:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import login from './login'
const store = new Vuex.Store({
	modules: { login }
})
// 导出方法
export function loadLocal() {
	store.dispatch('login/loadLocal')
}
export default store

最后在 main.js 中调用此方法即可,导入和使用语句如下:

import { loadLocal } from './store'
loadLocal()

现在我们在来看看效果,如图:

结语

这里只是给大家展示一种思路,具体的实现需要根据自己的业务来定,但是整体的流程都是差不多的

如果对于这个递归菜单,和后端部分这个实现登录逻辑部分,可以查看我后续发布的其他文章,或者如果我没忘记的话,我会来这里补上查看链接

到此这篇关于Vue如何根据角色获取菜单动态添加路由的文章就介绍到这了,更多相关Vue根据角色添加路由内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文