feat: 多角色登录权限控制

This commit is contained in:
WANGFAN\wangf 2024-11-09 15:37:37 +08:00
parent 32fd8caa50
commit 47f7bc2623
8 changed files with 146 additions and 79 deletions

View File

@ -17,9 +17,9 @@ service.interceptors.request.use(
if (localStorage.getItem("user-info")) {
userInfo = JSON.parse(localStorage.getItem("user-info") as string);
}
if (userInfo?.AdminToken) {
if (userInfo?.token) {
// 有token在请求头中携带token
config.headers.Authorization = userInfo.AdminToken;
config.headers.Authorization = userInfo.token;
}
return config;
},
@ -53,7 +53,7 @@ service.interceptors.response.use(
}
},
function (error: any) {
localStorage.removeItem("AdminToken");
localStorage.removeItem("token");
router.push("/login");
return Promise.reject(error);
}

View File

@ -112,6 +112,7 @@ import { useI18n } from "vue-i18n";
import { Modal } from "@arco-design/web-vue";
import { useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useUserInfoStore } from "@/store/modules/user-info";
import { useThemeConfig } from "@/store/modules/theme-config";
import { useThemeMethods } from "@/hooks/useThemeMethods";
@ -190,8 +191,12 @@ const logOut = () => {
closable: true,
onBeforeOk: async () => {
try {
// 退
const store = useUserInfoStore();
await store.logOut();
//
const route = useRoutesConfigStore();
await route.resetRoute();
router.replace("/login");
return true;
} catch {

View File

@ -19,3 +19,106 @@ export const resultError = (data: unknown, message: string, code = 500) => {
success: false
});
};
/**
*
* 1
* 2children则递归排序
* @param {array} tree
* @returns
*/
export const treeSort = (tree: Menu.MenuOptions[]) => {
if (!tree || tree.length == 0) return [];
tree.sort((a: Menu.MenuOptions, b: Menu.MenuOptions) => {
// a和b都是undefined则相等
if (a.meta.sort != 0 && !a.meta.sort && b.meta.sort != 0 && !b.meta.sort) {
return 0;
}
// a是undefined则a被排在b之后
if (a.meta.sort != 0 && !a.meta.sort) return 1;
// b是undefined则b被排在a之后
if (b.meta.sort != 0 && !b.meta.sort) return -1;
return a.meta.sort - b.meta.sort;
});
// 深层递归
return tree.map((item: any) => {
if (item?.children?.length > 0) {
item.children = treeSort(item.children);
}
return item;
});
};
/**
*
* 1访
* 2
* @param {array} tree
* @returns
*/
export const filterByRole = (tree: any, userRoles: Array<string>) => {
return tree.filter((item: any) => {
// 过滤角色权限
if (item?.meta?.roles) {
if (!roleBase(item.meta.roles, userRoles)) return false;
}
// 过滤是否禁用
if (item?.meta?.disable) return false;
if (item.children) item.children = filterByRole(item.children, userRoles);
return true;
});
};
/**
*
* @param {array} roles
* @returns true是 false否
*/
export const roleBase = (roles: Array<string>, userRoles: Array<string>) => {
return userRoles.some((item: string) => roles.includes(item));
};
/**
*
* @param { string } data
* @returns
*/
export function deepClone(data: any) {
let stack = [];
let cloned;
if (Array.isArray(data)) {
cloned = [];
} else if (typeof data === "object" && data !== null) {
cloned = {};
} else {
return data;
}
stack.push({
original: data,
copy: cloned
});
while (stack.length > 0) {
let current: any = stack.pop();
let original = current.original;
let copy = current.copy;
for (let key in original) {
if (original.hasOwnProperty(key)) {
let value = original[key];
if (typeof value === "object" && value !== null) {
copy[key] = Array.isArray(value) ? [] : {};
stack.push({
original: value,
copy: copy[key]
});
} else {
copy[key] = value;
}
}
}
}
return cloned;
}

View File

@ -1,5 +1,6 @@
import type { MockMethod } from "vite-plugin-mock";
import { resultSuccess, resultError } from "../_utils";
import { RouteRecordRaw } from "vue-router";
import { deepClone, filterByRole, treeSort, resultSuccess, resultError } from "../_utils";
import systemMenu from "../_data/system_menu";
/**
@ -29,67 +30,9 @@ export default [
let token = headers.authorization;
// 这里模拟两个角色admin、common
let userRoles = token === "Admin-Token" ? ["admin"] : ["common"];
systemMenu[0].children = treeSort(filterByRole(systemMenu[0].children, userRoles));
return resultSuccess(systemMenu);
const originTree: RouteRecordRaw[] = deepClone(systemMenu);
originTree[0].children = treeSort(filterByRole(originTree[0].children, userRoles));
return resultSuccess(originTree);
}
}
] as MockMethod[];
/**
*
* 1
* 2children则递归排序
* @param {array} tree
* @returns
*/
export const treeSort = (tree: Menu.MenuOptions[]) => {
if (!tree || tree.length == 0) return [];
tree.sort((a: Menu.MenuOptions, b: Menu.MenuOptions) => {
// a和b都是undefined则相等
if (a.meta.sort != 0 && !a.meta.sort && b.meta.sort != 0 && !b.meta.sort) {
return 0;
}
// a是undefined则a被排在b之后
if (a.meta.sort != 0 && !a.meta.sort) return 1;
// b是undefined则b被排在a之后
if (b.meta.sort != 0 && !b.meta.sort) return -1;
return a.meta.sort - b.meta.sort;
});
// 深层递归
return tree.map((item: any) => {
if (item?.children?.length > 0) {
item.children = treeSort(item.children);
}
return item;
});
};
/**
*
* 1访
* 2
* @param {array} tree
* @returns
*/
export const filterByRole = (tree: any, userRoles: Array<string>) => {
return tree.filter((item: any) => {
// 过滤角色权限
if (item?.meta?.roles) {
if (!roleBase(item.meta.roles, userRoles)) return false;
}
// 过滤是否禁用
if (item?.meta?.disable) return false;
if (item.children) item.children = filterByRole(item.children, userRoles);
return true;
});
};
/**
*
* @param {array} roles
* @returns true是 false否
*/
export const roleBase = (roles: Array<string>, userRoles: Array<string>) => {
return userRoles.some((item: string) => roles.includes(item));
};

View File

@ -7,12 +7,12 @@ export default [
url: "/mock/login",
method: "post",
timeout: 300,
response: ({ body }) => {
response: ({ body }: any) => {
let { username, password } = body;
if (username === "admin" && password === "123456") {
return resultSuccess({ token: "Admin-Token" });
}
if(username === "common" && password === "123456"){
if (username === "common" && password === "123456") {
return resultSuccess({ token: "Common-Token" });
}
return resultError(null, "账号或者密码错误", 500);
@ -22,11 +22,21 @@ export default [
url: "/mock/user/info",
method: "get",
timeout: 300,
response: () => {
let data = {
response: ({ headers }: any) => {
let data = {};
// 管理员角色
if (headers.authorization == "Admin-Token") {
data = {
username: "admin", // 用户名
roles: ["admin"] // 角色
};
} else {
// 普通角色
data = {
username: "common", // 用户名
roles: ["common"] // 角色
};
}
return resultSuccess(data);
}
}

View File

@ -40,16 +40,16 @@ const router = createRouter({
router.beforeEach(async (to, from, next) => {
NProgress.start(); // 开启进度条
const store = useUserInfoStore(pinia);
const { AdminToken } = storeToRefs(store);
const { token } = storeToRefs(store);
console.log("去", to, "来自", from);
// next()内部加了path等于跳转指定路由会再次触发router.beforeEach内部无参数等于放行不会触发router.beforeEach
if (to.path === "/login" && !AdminToken.value) {
if (to.path === "/login" && !token.value) {
// 1、去登录页无token放行
next();
} else if (!AdminToken.value) {
} else if (!token.value) {
// 2、没有token直接重定向到登录页
next("/login");
} else if (to.path === "/login" && AdminToken.value) {
} else if (to.path === "/login" && token.value) {
// 3、去登录页有token直接重定向到home页
next("/home");
// 项目内的跳转,处理跳转路由高亮

View File

@ -75,6 +75,12 @@ export const useRoutesConfigStore = defineStore("route-config", {
removeRouteNames(list: Array<string>) {
this.cacheRoutes = this.cacheRoutes.filter((item: string) => !list.includes(item));
},
/**
* routeTree路由树
*/
async resetRoute() {
this.routeTree = [];
},
/**
*
* 1

View File

@ -12,18 +12,18 @@ export const useUserInfoStore = defineStore("user-info", {
username: "",
roles: []
}, // 账号信息
AdminToken: "" // token
token: "" // token
}),
actions: {
async setAccount(data: Array<string>) {
this.account = data;
},
async setToken(data: string) {
this.AdminToken = data;
this.token = data;
},
async logOut() {
this.account = {};
this.AdminToken = "";
this.token = "";
}
},
persist: persistedstateConfig("user-info")