权限
项目中集成了两种权限处理方式:
通过用户角色来过滤菜单(前端方式控制)
通过后台来动态生成路由表(后台方式控制)
前端角色权限
TIP
该方式实现的原理是在前端固定写死路由的权限,指定路由有哪些权限可以查看,一开始只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内,在登陆后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,在通过 router.addRoutes 添加到路由实例,实现权限的过滤
WARNING
缺点权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统
实现
- 在项目配置将系统内权限模式改为
ROLE
模式
// ! 改动后需要清空浏览器缓存
const setting: ProjectConfig = {
// 权限模式
permissionMode: PermissionModeEnum.ROLE,
};
- 在路由表配置路由所需的权限,如果不配置,默认可见(见注释)
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { RoleEnum } from '/@/enums/roleEnum';
import { t } from '/@/hooks/web/useI18n';
const permission: AppRouteModule = {
path: '/permission',
name: 'Permission',
component: LAYOUT,
redirect: '/permission/front/page',
meta: {
icon: 'ion:key-outline',
title: t('routes.demo.permission.permission'),
},
children: [
{
path: 'front',
name: 'PermissionFrontDemo',
component: getParentLayout('PermissionFrontDemo'),
meta: {
title: t('routes.demo.permission.front'),
},
children: [
{
path: 'auth-pageA',
name: 'FrontAuthPageA',
component: () => import('/@/views/demo/permission/front/AuthPageA.vue'),
meta: {
title: t('routes.demo.permission.frontTestA'),
roles: [RoleEnum.SUPER],
},
},
{
path: 'auth-pageB',
name: 'FrontAuthPageB',
component: () => import('/@/views/demo/permission/front/AuthPageB.vue'),
meta: {
title: t('routes.demo.permission.frontTestB'),
roles: [RoleEnum.TEST],
},
},
],
},
],
};
export default permission;
- 在路由钩子内动态判断
详细代码见 src/router/guard/permissionGuard.ts
// 这里只列举了主要代码
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
permissionStore.setDynamicAddedRoute(true);
next(nextData);
permissionStore.buildRoutesAction
过滤动态路由 详细代码见 src/store/modules/permission.ts
// 主要代码
if (permissionMode === PermissionModeEnum.ROLE) {
const routeFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
const { roles } = meta || {};
if (!roles) return true;
return roleList.some((role) => roles.includes(role));
};
routes = filter(asyncRoutes, routeFilter);
routes = routes.filter(routeFilter);
// Convert multi-level routing to level 2 routing
routes = flatMultiLevelRoutes(routes);
}
动态更换角色
系统提供usePermission方便角色相关操作
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { changeRole } = usePermission();
// 更换为test角色
// 动态更改角色,传入角色名称,可以是数组
changeRole(RoleEnum.TEST);
return {};
},
});
细粒度权限
函数方式
usePermission还提供了按钮级别的权限控制。
<template>
<a-button v-if="hasPermission([RoleEnum.TEST, RoleEnum.SUPER])" color="error" class="mx-4">
拥有[test,super]角色权限可见
</a-button>
</template>
<script lang="ts">
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { hasPermission } = usePermission();
return { hasPermission };
},
});
</script>
组件方式
具体查看权限组件使用
指令方式
TIP
指令方式不能动态更改权限
<a-button v-auth="RoleEnum.SUPER" type="primary" class="mx-4"> 拥有super角色权限可见</a-button>
后台动态获取
TIP
该方式的实现原理是通过后台动态生成路由表,且遵循一定的结构返回给前端,如果不是前端需要自行转化为可识别的结构,在通过 router.addRoutes 添加到路由实例,实现权限的动态生成
实现
- 在项目配置将系统内权限模式改为
BACK
模式
// ! 改动后需要清空浏览器缓存
const setting: ProjectConfig = {
// 权限模式
permissionMode: PermissionModeEnum.BACK,
};
- 路由拦截,与角色权限模式一致
permissionStore.buildRoutesAction
过滤动态路由 详细代码见 /@/store/modules/permission.ts
// 主要代码
if (permissionMode === PermissionModeEnum.BACK) {
const { createMessage } = useMessage();
createMessage.loading({
content: t('sys.app.menuLoading'),
duration: 1,
});
// !Simulate to obtain permission codes from the background,
// this function may only need to be executed once, and the actual project can be put at the right time by itself
let routeList: AppRouteRecordRaw[] = [];
try {
this.changePermissionCode();
routeList = (await getMenuList()) as AppRouteRecordRaw[];
} catch (error) {
console.error(error);
}
// Dynamically introduce components
routeList = transformObjToRoute(routeList);
// Background routing to menu structure
const backMenuList = transformRouteToMenu(routeList);
this.setBackMenuList(backMenuList);
routeList = flatMultiLevelRoutes(routeList);
routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
}
getMenuListById 返回值格式
返回值是有多个路由模块所组成
[
{
path: '/home',
name: 'Home',
component: '/dashboard/welcome/index',
meta: {
title: 'routes.dashboard.welcome',
affix: true,
icon: 'ant-design:home-outlined',
},
},
{
path: '/permission',
name: 'Permission',
component: 'LAYOUT',
redirect: '/permission/front/page',
meta: {
icon: 'carbon:user-role',
title: 'routes.demo.permission.permission',
},
children: [
{
path: 'back',
name: 'PermissionBackDemo',
meta: {
title: 'routes.demo.permission.back',
},
children: [
{
path: 'page',
name: 'BackAuthPage',
component: '/demo/permission/back/index',
meta: {
title: 'routes.demo.permission.backPage',
},
},
{
path: 'btn',
name: 'BackAuthBtn',
component: '/demo/permission/back/Btn',
meta: {
title: 'routes.demo.permission.backBtn',
},
},
],
},
],
},
];
动态更换菜单
系统提供usePermission方便角色相关操作
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { changeMenu } = usePermission();
// 更改菜单的实现需要自行去修改
changeMenu();
return {};
},
});
细粒度权限
函数方式
usePermission还提供了按钮级别的权限控制。
<template>
<a-button v-if="hasPermission(['20000', '2000010'])" color="error" class="mx-4">
拥有[20000,2000010]code可见
</a-button>
</template>
<script lang="ts">
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { hasPermission } = usePermission();
return { hasPermission };
},
});
</script>
组件方式
具体查看权限组件使用
指令方式
TIP
指令方式不能动态更改权限
<a-button v-auth="'1000'" type="primary" class="mx-4"> 拥有code ['1000']权限可见 </a-button>
如何初始化 code
一般如果需要做按钮级别权限。后台会提供相应的 code。或者类型的判断标识
一般获取这些编码只需要在登录后获取一次即可。
import { getPermCodeByUserId } from '/@/api/sys/user';
import { permissionStore } from '/@/store/modules/permission';
async function changePermissionCode(userId: string) {
// 从后台获取当前用户拥有的编码
const codeList = await getPermCodeByUserId({ userId });
permissionStore.commitPermCodeListState(codeList);
}