Vue 路由权限[菜单权限/按钮权限控制]
前言
刚刚完工了做了半年的保险后台管理系统,系统整体业务比较复杂,这也是我在公司从 0 到 1 的 一个完整系统实践,做这个系统过程中踩了不少坑,也学到了很多,趁着空挡不忙,特意做一个总结。
其实我们在做的后台管理系统大多数基础框架都一样,后台管理系统主要的是角色权限管理,按钮权限管理和菜单管理,其它的业务主要围绕在这个基础之上进行扩展,最终 构成了符合业务的后台管理系统。
由于我们公司的项目都是采用 Vue 技术栈,所以文章也是围绕 Vue 如何进行权限管理 进行讲解。
权限授权登录
任何一个后台管理系统都是首先从登录开始,登录后返回用户基本信息,以及token
。
token
:存入sessionStronge/localStronge
中,然后加入到封装好的Axios
的请求头中,每次请求携带token
。- 用户基本信息
登录成功后同时要做很多事情,具体业务具体对待。后台管理系统登录成功后会请求当前用户的菜单权限接口,来获取用户的可访问的路由(动态路由),获取成功后, Vue Router 是不能直接使用的,必须得解析成符合 Vue Router 可识别的格式。
登录
handleLogin() { this.$refs.loginForm.validate(valid = >{ if (valid) { this.loading = true; login(this.loginForm).then(res = >{ if (res.code === 200) { // 存放 token sessionStorage.setItem("tokens", res.data.token); // 触发 Vuex 来加载获取当前用户的菜单,并解析路由 store.dispatch("setMenuList"); this.$message({ message: "登录成功", type: "success", duration: 1000 }); this.$router.replace({ path: "/dashboard" }); } }). catch(() = >{ this.loading = false; }); } else { console.log("error submit!!"); return false; } }); }
获取当前用户菜单,解析路由
登录成功后,本文通过 Vuex
来获取当前用户菜单和解析路由的。
store.dispatch("setMenuList");
// getMenu 解析后台路由 import { getMenu } from '../../utils/getMenu' // 引入路由 和 静态路由 import router, { constantRoutes } from '../../router/index' const state = { routerType: '', // 菜单路由 meunList: [] } const mutations = { SET_ROUTER_TYPE(state, type) { state.routerType = type }, SET_ROUTER_MENULIST(state, list) { // 静态路由 + 动态路由 合并 完整路由 const array = constantRoutes.concat(list) state.meunList = array router.options.routes = array router.addRoutes([...array]) } } const actions = { setMenuList({ commit, state }) { // 接收返回来的 路由数组 return new Promise((resolve, reject) => { getMenu().then(res => { commit('SET_ROUTER_TYPE', '') commit('SET_ROUTER_MENULIST', res) resolve(res) }) }) } } export default { state, mutations, actions }
解析后端返回来路由(重点)
封装好的解析后端返回来的路由,这块主要是为了在 Vuex
中使用。
import Layout from '@/layout' import {getUserAuthMenu} from '@/api/user' /** * @description: 解析后端返回来的菜单树 * @param {*} data 后端返回来的路由树 * @param {*} arr 菜单 * @return {*} */ function tree(data, arr) { data.forEach((datas, index) => { arr.push({ path: datas.path, name: datas.name, types: datas.types, hidden: datas.hidden == 'true' ? true : false, // 当时这块踩坑了 component: datas.component === 'Layout' ? Layout : resolve => require([`@/views/${datas.component}.vue`], resolve), meta: { title: datas.meta.title, icon: datas.meta.icon, // 用来存放按钮权限 button: datas.meta.button }, // redirect: datas.redirect, id: datas.id, // 子路由 children: [] }) if (datas.children) { const childArr = tree(datas.children, []) arr[index].children = childArr } }) return arr } /** * @description: 获取当前登录用户的菜单 * @param {*} * @return {*} */ export function getMenu() { return new Promise(function (resolve, reject) { getUserAuthMenu().then(res => { if(res.code === 200){ const datas = res.data // 调用 tree 来解析后端返回来的树 resolve(tree(datas, [])) } }) }) }
后端接收路由格式
const data =[ { name:'组件名', path:'根路径', types:'顶部菜单 type', component: Layout, hidden:true, // 是否在右侧栏显示 redirect:'重定向', meta:{ icon:'图标', title:'菜单名', button:'' }, children:[ { name:'组件名', path:'根路径', types:'顶部菜单 type', component: Layout, hidden: true, redirect:'重定向', meta:{ icon:'图标', title:'菜单名', button:'' } }, { name:'组件名', path:'根路径', types:'顶部菜单 type', component: Layout, hidden: true, redirect:'重定向', meta:{ icon:'图标', title:'菜单名', button:'' } } ] } ]
前端接收到的真实菜单树
[ { "id": 1, "name": "系统管理", "path": "/sys", "types": "systemcontrol", "component": "Layout", "hidden": true, "redirect": null, "icon": "eye", "title": "系统管理", "pid": 0, "meta": { "icon": "eye", "title": "系统管理", "button": [] }, "children": [ { "id": 4, "name": "菜单管理", "path": "/MenuManage", "types": "systemcontrol", "component": "MenuManage/index", "hidden": true, "redirect": null, "icon": "eyes", "title": "菜单管理", "pid": 1, "meta": { "icon": "eyes", "title": "菜单管理", //存放按钮权限 "button": [ "getAuthority", "menu_show", "menu_upd", "menu.del", "menu_bind", "menu add" ] }, "children": null, "operations": null }, { "id": 3, "name": "角色管理", "path": "/RoleManage", "types": "systemcontrol", "component": "RoleManage/index", "hidden": true, "redirect": null, "icon": "eye", "title": "角色管理", "pid": 1, "meta": { "icon": "eye", "title": "角色管理", "button": [ "role_getAll", "role.add", "role-get", "role.upd", "role.del", "role bind" ] }, "children": null, "operations": null }, { "id": 2, "name": "用户管理", "path": "/UserManage", "types": "systemcontrol", "component": "UserManage/index", "hidden": true, "redirect": null, "icon": "eye", "title": "用户管理", "pid": 1, "meta": { "icon": "eye", "title": "用户管理", "button": [ "user_add", "user_get", "user_upd", "user_get", "user.del", "upd_password", "reset_password" ] }, "children": null, "operations": null } ], "operations": null } ]
页面刷新,路由丢失
到此为止,已经实现了 Vue 动态权限控制,别高兴的太早,哈哈,一刷新页面,页面就进入了 404 页面。
这是为什么呢?
因为存入 Vuex 中的数据,一刷新页面,就会清空,那么当然找不到当前路由,就进入 404 页面了.
如何处理呢?
一、可以将静态和动态构成的完整路由存放在sessionStronge/localStronge
中,然后页面刷新时,通过在全局入口文件 App.vue 的生命周期created
中,将router=sessionStronge/localStronge
存入的完整的路由,页面在刷新时,它会重新加载完整的路由。
二、如果是使用 Vuex 来获取和解析用户菜单的话,那么你可以在全局入口文件 App.vue 的生命周期created
中,再次执行 Vuex Action 来重新加载用户菜单
我这块直接在 App.vue 的生命周期created
中,再次执行了Vuex
来进行加载和解析,没有做其它操作。当然了,具体业务具体对待。
<template> <div id="app"> <router-view v-if="isRouterAlive" /> </div> </template> <script> import store from "@/store"; export default { name: "App", provide() { return { reload: this.reload }; }, data() { return { isRouterAlive: true }; }, methods: { reload() { this.isRouterAlive = false; this.$nextTick(() => (this.isRouterAlive = true)); } }, created() { //只要刷新页面,就会重新加载路由树,保证了路由不会丢失数据 store.dispatch("setMenuList"); } }; </script>
总结
核心思想
1.定义符合 当前项目业务路由格式,前后端按这个接收传递。
2.前端解析后端返回的动态路由,生成 Vue Router 可识别格式,最后拼接完整路由。
3.刷新路由丢失处理。
按钮权限控制
1.当前组件 路由 携带可使用的 按钮权限,存入数组中,通过v-if
来判断是否显示。
2.登录时,单独获取整个系统的按钮权限,将获取到的所有按钮 存入一个数组中,放入全局中,然后,通过 v-if
来判断是否显示。
码云笔记 » Vue 路由权限[菜单权限/按钮权限控制]