在实际项目开发中,权限管理是一个关键功能,用于控制不同用户对系统资源的访问。权限是对特定资源的访问许可,权限控制的目的是确保用户只能访问到被分配的资源。例如,网站管理员可以对网站数据进行增删改查,而普通用户只能浏览。
权限管理的分类
根据功能的不同,权限控制可以分为以下几类:
菜单权限
菜单管理涉及定义和管理应用中的导航菜单。不同的用户角色可能会看到不同的菜单项,从而访问不同的功能模块。
路由权限
路由权限控制用户可以访问的页面和路径。
按钮权限
不同的用户角色可能享有不同的操作权限,例如管理员可以增删改查,而普通用户只能查看。
接口权限
接口权限通常采用 JWT
形式进行验证。如果请求未通过验证,服务器会返回 401
状态码,客户端则跳转到登录页面重新登录。登录成功后,客户端会拿到 token
并将其存储起来,通过 axios
请求拦截器在每次请求时携带 token
。
权限管理的实现方案
菜单权限控制
菜单权限可以通过以下两种方案实现:
方案一:菜单与路由分离
-
前端定义路由信息:
{ name: "login", path: "/login", component: () => import("@/pages/Login.vue") }
-
全局路由守卫:
function hasPermission(router, accessMenu) { if (whiteList.includes(router.path)) { return true; } const menu = Util.getMenuByName(router.name, accessMenu); return !!menu.name; } Router.beforeEach(async (to, from, next) => { if (getToken()) { const userInfo = store.state.user.userInfo; if (!userInfo.name) { try { await store.dispatch("GetUserInfo"); // 获取用户信息 await store.dispatch('updateAccessMenu'); // 更新访问菜单 if (to.path === '/login') { next({ name: 'home_index' }); // 如果当前路径是登录页,跳转到首页 } else { next({ ...to, replace: true }); // 替换当前路径 } } catch (e) { if (whiteList.includes(to.path)) { next(); // 如果路径在白名单中,直接通过 } else { next('/login'); // 否则跳转到登录页 } } } else { if (to.path === '/login') { next({ name: 'home_index' }); // 如果当前路径是登录页,跳转到首页 } else { if (hasPermission(to, store.getters.accessMenu)) { Util.toDefaultPage(store.getters.accessMenu, to, routes, next); // 跳转到默认页面 } else { next({ path: '/403', replace: true }); // 没有权限,跳转到403页面 } } } } else { if (whiteList.includes(to.path)) { next(); // 如果路径在白名单中,直接通过 } else { next('/login'); // 否则跳转到登录页 } } const menu = Util.getMenuByName(to.name, store.getters.accessMenu); Util.title(menu.title); // 设置页面标题 }); Router.afterEach((to) => { window.scrollTo(0, 0); // 滚动条回到顶部 });
优点:
- 前后端职责分明,前端负责路由定义,后端负责菜单管理。
缺点:
- 需要维护菜单与路由的对应关系,增加了复杂性。
- 每次路由跳转都需要进行权限判断。
方案二:菜单和路由都由后端返回
-
前端统一定义路由组件:
const Home = () => import("../pages/Home.vue"); const UserInfo = () => import("../pages/UserInfo.vue"); export default { home: Home, userInfo: UserInfo };
-
后端返回路由组件:
[ { name: "home", path: "/", component: "home" }, { name: "userinfo", path: "/userinfo", component: "userInfo" } ]
优点:
- 前后端高度集成,灵活性高。
缺点:
- 前后端配合要求更高。
- 每次路由跳转都需要进行权限判断。
路由权限控制
方案一:初始化即挂载全部路由
-
路由定义:
const routerMap = [ { path: '/permission', component: Layout, redirect: '/permission/index', alwaysShow: true, meta: { title: '权限管理', icon: 'lock', roles: ['admin', 'editor'] // 标记权限 }, children: [ { path: 'page', component: () => import('@/views/permission/page'), name: 'pagePermission', meta: { title: '页面权限', roles: ['admin'] // 标记权限 } }, { path: 'directive', component: () => import('@/views/permission/directive'), name: 'directivePermission', meta: { title: '指令权限' // 如果没有设置权限标识,意味着这个页面不需要权限 } } ] } ];
优点:
- 简单直观,易于实现。
缺点:
- 加载所有路由,对性能有影响。
- 每次路由跳转都需要进行权限判断。
- 菜单写死在前端,修改显示文字或权限信息需要重新编译。
- 菜单与路由耦合,定义路由时需要添加菜单显示标题、图标等信息。
方案二:按需挂载路由
-
初始化挂载不需要权限控制的路由:
import router from './router'; import store from './store'; import { Message } from 'element-ui'; import NProgress from 'nprogress'; // 进度条 import 'nprogress/nprogress.css'; // 进度条样式 import { getToken } from '@/utils/auth'; // 从 cookie 获取 token NProgress.configure({ showSpinner: false }); // 配置进度条 function hasPermission(roles, permissionRoles) { if (roles.includes('admin')) return true; // 管理员权限直接通过 if (!permissionRoles) return true; return roles.some(role => permissionRoles.includes(role)); } const whiteList = ['/login', '/authredirect']; // 不需要重定向的白名单 router.beforeEach((to, from, next) => { NProgress.start(); // 开始进度条 if (getToken()) { // 确定是否有 token if (to.path === '/login') { next({ path: '/' }); NProgress.done(); // 如果当前页面是仪表盘,不会触发 afterEach 钩子,所以手动处理 } else { if (store.getters.roles.length === 0) { // 用户信息 store.dispatch('GetUserInfo').then(res => { // 获取用户信息 const roles = res.data.roles; // 注意:角色必须是一个数组!例如:['editor','develop'] store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成路由 router.addRoutes(store.getters.addRouters); // 添加路由 next({ ...to, replace: true }); // 替换当前路径,防止留下历史记录 }); }).catch((err) => { store.dispatch('FedLogOut').then(() => { Message.error(err || '验证失败,请重新登录'); next({ path: '/' }); }); }); } else { if (hasPermission(store.getters.roles, to.meta.roles)) { next(); // 有权限,继续跳转 } else { next({ path: '/401', replace: true, query: { noGoBack: true }}); // 没有权限,跳转到401页面 } } } } else { if (whiteList.includes(to.path)) { next(); // 如果路径在白名单中,直接通过 } else { next('/login'); // 否则跳转到登录页 NProgress.done(); // 如果当前页面是登录页,不会触发 afterEach 钩子,所以手动处理 } } }); router.afterEach(() => { NProgress.done(); // 结束进度条 });
优点:
- 按需加载路由,提升性能。
- 灵活性高,适合复杂的权限管理需求。
缺点:
- 每次路由跳转都需要进行权限判断。
- 菜单信息写死在前端,修改显示文字或权限信息需要重新编译。
- 菜单与路由耦合,定义路由时需要添加菜单显示标题、图标等信息。
按钮权限
方案一:使用 v-if
判断
-
优点:
- 简单直观,易于实现。
-
缺点:
- 如果页面较多,每个页面都需要获取用户权限并进行判断,增加复杂性。
方案二:通过自定义指令进行按钮权限判断
-
配置路由:
{ path: '/permission', component: Layout, name: ' ', meta: { btnPermissions: ['admin', 'supper', 'normal'] }, children: [ { path: 'supper', component: _import('system/supper'), name: ' ', meta: { btnPermissions: ['admin', 'supper'] } }, { path: 'normal', component: _import('system/normal'), name: ' ', meta: { btnPermissions: ['admin'] } } ] }
-
自定义权限鉴定指令:
import Vue from 'vue'; const has = Vue.directive('has', { bind: function (el, binding, vnode) { let btnPermissionsArr = binding.value ? [binding.value] : vnode.context.$route.meta.btnPermissions; if (!Vue.prototype.$_has(btnPermissionsArr)) { el.parentNode.removeChild(el); // 移除元素 } } }); Vue.prototype.$_has = function (value) { const btnPermissionsStr = sessionStorage.getItem("btnPermissions"); if (!btnPermissionsStr) { return false; } return value.includes(btnPermissionsStr); }; export { has };
-
使用自定义指令:
<el-button @click='editClick' type="primary" v-has>编辑</el-button>
优点:
- 代码简洁,易于维护。
缺点:
- 需要维护按钮权限信息。
接口权限
接口权限通常采用 JWT 形式进行验证。如果请求未通过验证,服务器会返回 401 状态码,客户端则跳转到登录页面重新登录。登录成功后,客户端会拿到 token 并将其存储起来,通过 axios 请求拦截器在每次请求时携带 token。
axios.interceptors.request.use(config => {
config.headers['Authorization'] = `Bearer ${cookie.get('token')}`; // 在请求头中携带 token
return config;
});
总结
权限管理是项目开发中不可或缺的一部分,合理的权限控制可以提升系统的安全性。通过上述方案,可以根据项目的具体需求选择合适的权限管理方式。
标签:vue,next,菜单,跳转,path,权限,路由 From: https://blog.csdn.net/misstianyun/article/details/143584187