基于角色的权限管理。
在这种方式下,系统中的用户可以被分配不同的角色,每个角色具有不同的权限。通过将用户与角色进行关联,可以实现对用户的权限控制。具体的实现步骤如下:
- 定义角色和权限:首先需要定义系统中的角色和每个角色所具有的权限。权限可以按照功能模块或者操作类型进行划分。
- 用户角色关联:将用户与角色进行关联,可以通过在用户表中添加一个角色ID字段来实现。当用户登录系统时,可以根据用户的角色ID获取用户所具有的权限。
- 路由权限控制:在Vue中,可以通过路由来控制用户的权限。可以在路由配置中为每个路由设置一个meta字段,用于表示该路由所需要的权限。在用户登录后,可以根据用户的权限动态生成路由表,并将其传递给Vue Router进行路由配置。
- 页面权限控制:在Vue中,可以通过指令或者组件的方式来实现页面级别的权限控制。可以为每个需要进行权限控制的组件添加一个v-if指令,根据用户的权限来判断是否显示该组件。
实现
1、路由定义界面操作
界面上的操作定义在路由meta上
const dataRouter: Array<RouteRecordRaw> = [
{
path: "/resourceManage",
component: Layout,
redirect: "/resourceManage/filesCatalog",
children: [
{
path: "/resourceManage/filesCatalog",
name: "files",
component: () => import("@/views/resourceManage/filesCatalog/index.vue"),
meta: {
icon: "Reading",
keepAlive: true,
requiresAuth: true,
title: "资源管理",
key: "files",
features: {//路由上定义界面操作
add: "新增资源",
pubDel: "批量删除",
edit: "编辑",
upload: "上传",
import: "导入",
},
},
},
],
},
];
2、角色权限配置
把路由meta定义的操作显示到界面上,操作权限
import dataRouter from "@/routers/modules/dataManage";
import { RouteRecordRaw } from "vue-router";
export default function useUiBtnPress() {
type btnUiPerssType = { [key: string]: any[] };
type featuresType = { [key: string]: string };
const btnUiPerss: btnUiPerssType = {};
type btnFeaturesType = { [key: string]: string[] };
const btnFeatures: btnFeaturesType = {};
const featuresForm = (router: Array<RouteRecordRaw>) => { //转化成和后端商量好的格式
let res = false;
for (let rou of router) {
if ("children" in rou && rou.children?.length) {
featuresForm(rou.children); //有children递归
} else if ("features" in rou.meta!) {
//有features改变结构
btnUiPerss[rou.path] = [];
btnFeatures[rou.path] = [];
for (let feat in rou.meta.features as featuresType) {
btnUiPerss[rou.path].push({
directive: feat,
depict: (rou.meta.features as featuresType)[feat],
});
btnFeatures[rou.path].push(feat);
}
} else res = true;
}
if (res) return; //都没有features退出递归
};
featuresForm(dataRouter);
console.log(btnUiPerss);
console.log(btnFeatures);
// UI 界面按钮权限字段,转化后的格式
// const btnUiPerss: any = {
// "/resourceManage/filesCatalog": [
// {
// directive: "add",
// depict: "新增资源"
// },
// {
// directive: "edit",
// depict: "编辑"
// },
//......省略了
// ],
// };
// const btnFeatures: any = {
// "/resourceManage/filesCatalog": ["add", "edit", "upload", "pubDel","import"],
// };
return {
btnUiPerss,
btnFeatures,
};
}
import useUiBtnPress from "@/hooks/useUiBtnPres";
const { btnUiPerss } = useUiBtnPress();
//根据各自界面进行渲染,编辑权限
界面效果
3、登录获取角色id
修改权限后,用户登录后,获取角色id存储在本地
(本项目存在localStorage中,全局配置piniaPersistStorage: window.localStorage)
import { defineStore, createPinia } from "pinia";
import { GlobalState } from "./interface";
import { UserInfo } from "@/api/interface";
import { meApi } from "@/api/modules/auth";//用户信息接口
import { Config } from "@/config";
import { piniaPersistConfig } from "@/store/helper/pinia";//pinia本地持久化
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
//import Cookies from "js-cookie";
import { ElMessage } from "element-plus";
export const GlobalStore = defineStore({
// id: 必须的,在所有 Store 中唯一
id: "GlobalState",
// state: 返回对象的函数
state: (): GlobalState => ({
// token
token: "",
// userInfo
userInfo: null,
}),
getters: {
// 用户是否登录
isAuthenticated(state): boolean {
if (!Config.authEnable) {
return true;
}
// 检查token或是session存在
if (!state.userInfo) {
return false;
}
return true;
},
},
actions: {
// setToken
setToken(token: string) {
this.token = token;
},
// setUserInfo
setUserInfo(userInfo: UserInfo | null) {
this.userInfo = userInfo;
},
// 更新用户信息
updateUserInfo(realName?: string) {
if (!Config.authEnable) {
this.userInfo = { id: 0, username: "", real_name: "", role_list: [], status: 1 };
}
// 检查token或是session存在
//if (!this.token && !Cookies.get("session")) {
// return;
//}
// 检查用户信息存在
if (!this.userInfo) {
// 只存在登录信息,但是没有用户信息,尝试获取用户信息
meApi()
.then(res => {
this.setUserInfo(res);
})
.catch(err => {
// 错误处理
ElMessage.error(err.response.data.message);
//err.response.data
//err.response.status
});
}
if (realName) {
this.userInfo.real_name = realName;
}
},
},
persist: piniaPersistConfig("GlobalState"),
});
// piniaPersist(持久化)
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;
4、获取角色权限
在layout布局组件根据角色id获取用户权限,存放权限路由,权限菜单,权限按钮
//布局hook,useLayout
import { computed, onMounted } from "vue";
import { useRoute } from "vue-router";
import { MenuStore } from "@/store/modules/menu";
import { AuthStore } from "@/store/modules/auth";
import { GlobalStore } from "@/store/index";
import { getPermissionApi, getRolePermissionApi } from "@/api/modules/auth";
import { handleRouter, filterRouter, filterPermissionRouter, createMenu } from "@/routers/util";
import { routes } from "@/routers/router";
import { Config } from "@/config";
export default function () {
const route = useRoute();
const menuStore = MenuStore();
const authStore = AuthStore();
const globalStore = GlobalStore();
onMounted(async () => {
// 获取菜单列表
let id=globalStore.userInfo.id
const res = await getRolePermissionApi(id); //获取角色权限
if (!res) return;
authStore.setSuper(res.super || false);//super为true就是有全部权限,不用过滤
// 生成需要渲染到目录中的路由树
if (res.routers) {
// 过滤生成需要渲染的路径树,filterRouter,filterPermissionRouter,createMenu三个工具函数,看思路就好
const data = filterRouter(routes, Config.availability || []);
// 使用权限过滤生成需要渲染的路径树
const data2 = filterPermissionRouter(data, res.routers || [], res.super || false);
const menu = createMenu(data2);
const dynamicRouter = handleRouter(menu);
authStore.setAuthRouter(dynamicRouter);//路由
menuStore.setMenuList(menu);//菜单
}
// 生成页面中的要素权限
if (res.features) {
authStore.setAuthButtons(res.features);//按钮
}
});
const activeMenu = computed((): string => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
const isCollapse = computed((): boolean => menuStore.isCollapse);
const accordion = computed((): boolean => menuStore.accordion);
const menuList = computed((): Menu.MenuOptions[] => menuStore.menuList);
return {
menuList,//布局渲染菜单
isCollapse,
accordion,
activeMenu,
};
}
5、菜单控制
layout.vue根据useLayout返回的menuList权限菜单进行渲染
6、按钮控制(vue自定义权限指令)
//自定义按钮权限指令
import { AuthStore } from "@/store/modules/auth";
import { Directive, toRaw } from "vue";
type UiPer = { [key: string]: string[] };
export const permission: Directive = {
mounted(el, binding) {
const authStore = AuthStore();
//value按钮上的权限
const value: string[] = binding.value;
//获取用户所有的权限
const permissions: UiPer = toRaw(authStore.authButtons);//角色按钮权限
const sup = toRaw(authStore.super);
if (sup) return;
//判断传递进来的按钮权限,是否存在
if (value && value instanceof Array && value.length > 0) {
const vd = value[0].split(":");
//判断传递进来的按钮权限字段,是否存在当前用户的permissions
const hasPermission = () => {
if (permissions[vd[0]]) {
return permissions[vd[0]].includes(vd[1]);
}
return false;
};
if (!hasPermission()) {
//没有权限时,隐藏
el.style.display = "none";
}
} else {
throw new Error("need roles! Like v-permission=\"['/mapManager/tile:add']\"");
}
},
};
export default permission;
没有特定权限按钮就不会显示
<el-button
size="small"
type="danger"
:icon="Delete"
@click="handleInitDele('one', scope.row.id)"
v-permission="['/modelManage/model:delete']"
>删除
</el-button>