feat: 多角色登录权限控制
This commit is contained in:
parent
32fd8caa50
commit
47f7bc2623
@ -17,9 +17,9 @@ service.interceptors.request.use(
|
|||||||
if (localStorage.getItem("user-info")) {
|
if (localStorage.getItem("user-info")) {
|
||||||
userInfo = JSON.parse(localStorage.getItem("user-info") as string);
|
userInfo = JSON.parse(localStorage.getItem("user-info") as string);
|
||||||
}
|
}
|
||||||
if (userInfo?.AdminToken) {
|
if (userInfo?.token) {
|
||||||
// 有token,在请求头中携带token
|
// 有token,在请求头中携带token
|
||||||
config.headers.Authorization = userInfo.AdminToken;
|
config.headers.Authorization = userInfo.token;
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
@ -53,7 +53,7 @@ service.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function (error: any) {
|
function (error: any) {
|
||||||
localStorage.removeItem("AdminToken");
|
localStorage.removeItem("token");
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,6 +112,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
import { Modal } from "@arco-design/web-vue";
|
import { Modal } from "@arco-design/web-vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useRoutesConfigStore } from "@/store/modules/route-config";
|
||||||
import { useUserInfoStore } from "@/store/modules/user-info";
|
import { useUserInfoStore } from "@/store/modules/user-info";
|
||||||
import { useThemeConfig } from "@/store/modules/theme-config";
|
import { useThemeConfig } from "@/store/modules/theme-config";
|
||||||
import { useThemeMethods } from "@/hooks/useThemeMethods";
|
import { useThemeMethods } from "@/hooks/useThemeMethods";
|
||||||
@ -190,8 +191,12 @@ const logOut = () => {
|
|||||||
closable: true,
|
closable: true,
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
|
// 用户退出
|
||||||
const store = useUserInfoStore();
|
const store = useUserInfoStore();
|
||||||
await store.logOut();
|
await store.logOut();
|
||||||
|
// 重置路由树
|
||||||
|
const route = useRoutesConfigStore();
|
||||||
|
await route.resetRoute();
|
||||||
router.replace("/login");
|
router.replace("/login");
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -19,3 +19,106 @@ export const resultError = (data: unknown, message: string, code = 500) => {
|
|||||||
success: false
|
success: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由树递归排序
|
||||||
|
* 1、先给当前层排序
|
||||||
|
* 2、若当前层有children则递归排序
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { MockMethod } from "vite-plugin-mock";
|
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";
|
import systemMenu from "../_data/system_menu";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,67 +30,9 @@ export default [
|
|||||||
let token = headers.authorization;
|
let token = headers.authorization;
|
||||||
// 这里模拟两个角色,admin、common
|
// 这里模拟两个角色,admin、common
|
||||||
let userRoles = token === "Admin-Token" ? ["admin"] : ["common"];
|
let userRoles = token === "Admin-Token" ? ["admin"] : ["common"];
|
||||||
systemMenu[0].children = treeSort(filterByRole(systemMenu[0].children, userRoles));
|
const originTree: RouteRecordRaw[] = deepClone(systemMenu);
|
||||||
return resultSuccess(systemMenu);
|
originTree[0].children = treeSort(filterByRole(originTree[0].children, userRoles));
|
||||||
|
return resultSuccess(originTree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
] as MockMethod[];
|
] as MockMethod[];
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由树递归排序
|
|
||||||
* 1、先给当前层排序
|
|
||||||
* 2、若当前层有children则递归排序
|
|
||||||
* @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));
|
|
||||||
};
|
|
||||||
|
|||||||
@ -7,12 +7,12 @@ export default [
|
|||||||
url: "/mock/login",
|
url: "/mock/login",
|
||||||
method: "post",
|
method: "post",
|
||||||
timeout: 300,
|
timeout: 300,
|
||||||
response: ({ body }) => {
|
response: ({ body }: any) => {
|
||||||
let { username, password } = body;
|
let { username, password } = body;
|
||||||
if (username === "admin" && password === "123456") {
|
if (username === "admin" && password === "123456") {
|
||||||
return resultSuccess({ token: "Admin-Token" });
|
return resultSuccess({ token: "Admin-Token" });
|
||||||
}
|
}
|
||||||
if(username === "common" && password === "123456"){
|
if (username === "common" && password === "123456") {
|
||||||
return resultSuccess({ token: "Common-Token" });
|
return resultSuccess({ token: "Common-Token" });
|
||||||
}
|
}
|
||||||
return resultError(null, "账号或者密码错误", 500);
|
return resultError(null, "账号或者密码错误", 500);
|
||||||
@ -22,11 +22,21 @@ export default [
|
|||||||
url: "/mock/user/info",
|
url: "/mock/user/info",
|
||||||
method: "get",
|
method: "get",
|
||||||
timeout: 300,
|
timeout: 300,
|
||||||
response: () => {
|
response: ({ headers }: any) => {
|
||||||
let data = {
|
let data = {};
|
||||||
username: "admin", // 用户名
|
// 管理员角色
|
||||||
roles: ["admin"] // 角色
|
if (headers.authorization == "Admin-Token") {
|
||||||
};
|
data = {
|
||||||
|
username: "admin", // 用户名
|
||||||
|
roles: ["admin"] // 角色
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 普通角色
|
||||||
|
data = {
|
||||||
|
username: "common", // 用户名
|
||||||
|
roles: ["common"] // 角色
|
||||||
|
};
|
||||||
|
}
|
||||||
return resultSuccess(data);
|
return resultSuccess(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,16 +40,16 @@ const router = createRouter({
|
|||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
NProgress.start(); // 开启进度条
|
NProgress.start(); // 开启进度条
|
||||||
const store = useUserInfoStore(pinia);
|
const store = useUserInfoStore(pinia);
|
||||||
const { AdminToken } = storeToRefs(store);
|
const { token } = storeToRefs(store);
|
||||||
console.log("去", to, "来自", from);
|
console.log("去", to, "来自", from);
|
||||||
// next()内部加了path等于跳转指定路由会再次触发router.beforeEach,内部无参数等于放行,不会触发router.beforeEach
|
// next()内部加了path等于跳转指定路由会再次触发router.beforeEach,内部无参数等于放行,不会触发router.beforeEach
|
||||||
if (to.path === "/login" && !AdminToken.value) {
|
if (to.path === "/login" && !token.value) {
|
||||||
// 1、去登录页,无token,放行
|
// 1、去登录页,无token,放行
|
||||||
next();
|
next();
|
||||||
} else if (!AdminToken.value) {
|
} else if (!token.value) {
|
||||||
// 2、没有token,直接重定向到登录页
|
// 2、没有token,直接重定向到登录页
|
||||||
next("/login");
|
next("/login");
|
||||||
} else if (to.path === "/login" && AdminToken.value) {
|
} else if (to.path === "/login" && token.value) {
|
||||||
// 3、去登录页,有token,直接重定向到home页
|
// 3、去登录页,有token,直接重定向到home页
|
||||||
next("/home");
|
next("/home");
|
||||||
// 项目内的跳转,处理跳转路由高亮
|
// 项目内的跳转,处理跳转路由高亮
|
||||||
|
|||||||
@ -75,6 +75,12 @@ export const useRoutesConfigStore = defineStore("route-config", {
|
|||||||
removeRouteNames(list: Array<string>) {
|
removeRouteNames(list: Array<string>) {
|
||||||
this.cacheRoutes = this.cacheRoutes.filter((item: string) => !list.includes(item));
|
this.cacheRoutes = this.cacheRoutes.filter((item: string) => !list.includes(item));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 重置routeTree路由树
|
||||||
|
*/
|
||||||
|
async resetRoute() {
|
||||||
|
this.routeTree = [];
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 路由初始化
|
* 路由初始化
|
||||||
* 1、将模块设置为真实模块
|
* 1、将模块设置为真实模块
|
||||||
|
|||||||
@ -12,18 +12,18 @@ export const useUserInfoStore = defineStore("user-info", {
|
|||||||
username: "",
|
username: "",
|
||||||
roles: []
|
roles: []
|
||||||
}, // 账号信息
|
}, // 账号信息
|
||||||
AdminToken: "" // token
|
token: "" // token
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async setAccount(data: Array<string>) {
|
async setAccount(data: Array<string>) {
|
||||||
this.account = data;
|
this.account = data;
|
||||||
},
|
},
|
||||||
async setToken(data: string) {
|
async setToken(data: string) {
|
||||||
this.AdminToken = data;
|
this.token = data;
|
||||||
},
|
},
|
||||||
async logOut() {
|
async logOut() {
|
||||||
this.account = {};
|
this.account = {};
|
||||||
this.AdminToken = "";
|
this.token = "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
persist: persistedstateConfig("user-info")
|
persist: persistedstateConfig("user-info")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user