refactor(auth): 权限判断与会话失效跳转收敛到 user store
- src/utils/auth: hasPerm 简化为纯权限判断,移除 redirectToLogin 与路由依赖 - src/directives/permission: v-hasPerm 复用 utils/auth 的 hasPerm - src/utils/request: token 失效改走 userStore.redirectToLogin - src/views/profile: 改密后重新登录改走 userStore.redirectToLogin
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
import { useUserStore } from "@/stores";
|
||||
import { ROLE_ROOT } from "@/constants";
|
||||
import { hasPerm as hasPermFn } from "@/utils/auth";
|
||||
|
||||
/**
|
||||
* 按钮权限
|
||||
* 按钮权限(复用 utils/auth 的 hasPerm 函数,保持指令与函数行为一致)
|
||||
*/
|
||||
export const hasPerm: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
@@ -17,20 +17,8 @@ export const hasPerm: Directive = {
|
||||
);
|
||||
}
|
||||
|
||||
const { roles, perms } = useUserStore().userInfo;
|
||||
|
||||
// 超级管理员拥有所有权限,如果是"*:*:*"权限标识,则不需要进行权限校验
|
||||
if (roles.includes(ROLE_ROOT) || requiredPerms.includes("*:*:*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
const hasAuth = Array.isArray(requiredPerms)
|
||||
? requiredPerms.some((perm) => perms.includes(perm))
|
||||
: perms.includes(requiredPerms);
|
||||
|
||||
// 如果没有权限,移除该元素
|
||||
if (!hasAuth && el.parentNode) {
|
||||
if (!hasPermFn(requiredPerms) && el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Storage } from "./storage";
|
||||
import { STORAGE_KEYS, ROLE_ROOT } from "@/constants";
|
||||
import { useUserStoreHook } from "@/stores/user";
|
||||
import router from "@/router";
|
||||
|
||||
// 负责本地凭证与偏好的读写
|
||||
export const AuthStorage = {
|
||||
@@ -45,58 +44,21 @@ export const AuthStorage = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 权限判断
|
||||
* 权限判断(v-hasPerm 指令共用本实现)
|
||||
*/
|
||||
export function hasPerm(value: string | string[], type: "button" | "role" = "button"): boolean {
|
||||
export function hasPerm(value: string | string[]): boolean {
|
||||
const { roles, perms } = useUserStoreHook().userInfo;
|
||||
|
||||
if (!roles || !perms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 超级管理员拥有所有权限
|
||||
if (type === "button" && roles.includes(ROLE_ROOT)) {
|
||||
const requiredPerms = Array.isArray(value) ? value : [value];
|
||||
|
||||
// 超级管理员或通配权限直接放行
|
||||
if (roles.includes(ROLE_ROOT) || requiredPerms.includes("*:*:*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auths = type === "button" ? perms : roles;
|
||||
return typeof value === "string"
|
||||
? auths.includes(value)
|
||||
: value.some((perm) => auths.includes(perm));
|
||||
}
|
||||
|
||||
let redirectingToLogin = false;
|
||||
|
||||
/**
|
||||
* 重定向到登录页面
|
||||
*/
|
||||
export async function redirectToLogin(
|
||||
message: string = "请重新登录",
|
||||
notify: boolean = true
|
||||
): Promise<void> {
|
||||
if (redirectingToLogin) return;
|
||||
redirectingToLogin = true;
|
||||
|
||||
if (notify) {
|
||||
ElNotification({
|
||||
title: "提示",
|
||||
message,
|
||||
type: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
await useUserStoreHook().resetAllState();
|
||||
|
||||
try {
|
||||
// 跳转到登录页,保留当前路由用于登录后跳转
|
||||
const currentPath = router.currentRoute.value.fullPath;
|
||||
await router.push(`/login?redirect=${encodeURIComponent(currentPath)}`);
|
||||
} catch (error) {
|
||||
console.error("Redirect to login error:", error);
|
||||
// 强制跳转,即使路由重定向失败
|
||||
window.location.href = "/login";
|
||||
} finally {
|
||||
redirectingToLogin = false;
|
||||
}
|
||||
return requiredPerms.some((perm) => perms.includes(perm));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs from "qs";
|
||||
import { ApiCodeEnum } from "@/enums/api";
|
||||
import { useUserStoreHook } from "@/stores/user";
|
||||
import { usePermissionStoreHook } from "@/stores/permission";
|
||||
import { AuthStorage, redirectToLogin } from "@/utils/auth";
|
||||
import { AuthStorage } from "@/utils/auth";
|
||||
import type { ApiResult } from "@/api/common";
|
||||
|
||||
// 防止同一请求在 token 刷新后重复进入重试,导致死循环
|
||||
@@ -65,15 +65,15 @@ http.interceptors.response.use(
|
||||
|
||||
// Token 过期
|
||||
if (code === ApiCodeEnum.ACCESS_TOKEN_INVALID) {
|
||||
const userStore = useUserStoreHook();
|
||||
if (!config || retriedRequests.has(config)) {
|
||||
await redirectToLogin("登录已过期,请重新登录");
|
||||
await userStore.redirectToLogin("登录已过期,请重新登录");
|
||||
return Promise.reject(new Error("Token Invalid"));
|
||||
}
|
||||
|
||||
retriedRequests.add(config);
|
||||
|
||||
try {
|
||||
const userStore = useUserStoreHook();
|
||||
await userStore.refreshTokenOnce();
|
||||
|
||||
const token = AuthStorage.getAccessToken();
|
||||
@@ -83,14 +83,14 @@ http.interceptors.response.use(
|
||||
|
||||
return http(config);
|
||||
} catch {
|
||||
await redirectToLogin("登录已过期,请重新登录");
|
||||
await userStore.redirectToLogin("登录已过期,请重新登录");
|
||||
return Promise.reject(new Error("Token refresh failed"));
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh token 失效
|
||||
if (code === ApiCodeEnum.REFRESH_TOKEN_INVALID) {
|
||||
await redirectToLogin("登录已过期,请重新登录", false);
|
||||
await useUserStoreHook().redirectToLogin("登录已过期,请重新登录", false);
|
||||
return Promise.reject(new Error("Token Invalid"));
|
||||
}
|
||||
|
||||
|
||||
@@ -314,7 +314,6 @@ import type { Component } from "vue";
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||
import FileAPI from "@/api/file";
|
||||
import { useUserStoreHook } from "@/stores";
|
||||
import { redirectToLogin } from "@/utils/auth";
|
||||
|
||||
import {
|
||||
Calendar,
|
||||
@@ -856,7 +855,7 @@ const handleSubmit = async () => {
|
||||
|
||||
await UserAPI.changePassword(passwordChangeForm);
|
||||
dialogState.visible = false;
|
||||
await redirectToLogin("密码已修改,请重新登录");
|
||||
await userStore.redirectToLogin("密码已修改,请重新登录");
|
||||
} else if (dialogState.type === DialogType.MOBILE) {
|
||||
const valid = await mobileBindingFormRef.value?.validate();
|
||||
if (!valid) return;
|
||||
|
||||
Reference in New Issue
Block a user