diff --git a/frontend/src/components/AuthGuard.tsx b/frontend/src/components/AuthGuard.tsx
new file mode 100644
index 00000000..af548306
--- /dev/null
+++ b/frontend/src/components/AuthGuard.tsx
@@ -0,0 +1,112 @@
+import { useState, useEffect, useCallback } from "react";
+import { message } from "antd";
+import { LoginDialog } from "@/pages/Layout/LoginDialog";
+import { SignupDialog } from "@/pages/Layout/SignupDialog";
+import { post, get } from "@/utils/request";
+import { useTranslation } from "react-i18next";
+
+function loginUsingPost(data: { username: string; password: string }) {
+ return post("/api/user/login", data);
+}
+
+function signupUsingPost(data: { username: string; email: string; password: string }) {
+ return post("/api/user/signup", data);
+}
+
+export function AuthGuard() {
+ const { t } = useTranslation();
+ const [loginOpen, setLoginOpen] = useState(false);
+ const [signupOpen, setSignupOpen] = useState(false);
+ const [loading, setLoading] = useState(false);
+
+ const openLoginDialog = useCallback(() => {
+ console.log('[AuthGuard] openLoginDialog called, setting loginOpen to true');
+ setLoginOpen(true);
+ }, []);
+
+ const openSignupDialog = useCallback(() => {
+ console.log('[AuthGuard] openSignupDialog called');
+ setSignupOpen(true);
+ }, []);
+
+ useEffect(() => {
+ console.log('[AuthGuard] Registering show-login event listener');
+ window.addEventListener("show-login", openLoginDialog);
+
+ return () => {
+ console.log('[AuthGuard] Removing show-login event listener');
+ window.removeEventListener("show-login", openLoginDialog);
+ };
+ }, [openLoginDialog]);
+
+ useEffect(() => {
+ const session = localStorage.getItem("session");
+ if (!session) {
+ get("/api/sys-param/sys.home.page.url").catch(() => {});
+ }
+ }, []);
+
+ const handleLogin = async (values: { username: string; password: string }) => {
+ try {
+ setLoading(true);
+ const response = await loginUsingPost(values);
+ localStorage.setItem("session", JSON.stringify(response.data));
+ message.success(t("user.messages.loginSuccess"));
+ setLoginOpen(false);
+ window.location.reload();
+ } catch (error) {
+ console.error("Login error:", error);
+ message.error(t("user.messages.loginFailed"));
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSignup = async (values: {
+ username: string;
+ email: string;
+ password: string;
+ confirmPassword: string;
+ }) => {
+ if (values.password !== values.confirmPassword) {
+ message.error(t("user.messages.passwordMismatch"));
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const { username, email, password } = values;
+ const response = await signupUsingPost({ username, email, password });
+ message.success(t("user.messages.signupSuccess"));
+ localStorage.setItem("session", JSON.stringify(response.data));
+ setSignupOpen(false);
+ window.location.reload();
+ } catch (error) {
+ console.error("Registration error:", error);
+ message.error(t("user.messages.signupFailed"));
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export default AuthGuard;
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index f1d826e6..d01d1321 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -5,6 +5,7 @@ import router from "./routes/routes";
import { App as AntdApp, Spin, ConfigProvider } from "antd";
import "./index.css";
import TopLoadingBar from "./components/TopLoadingBar";
+import AuthGuard from "./components/AuthGuard";
import { store } from "./store";
import { Provider } from "react-redux";
import theme from "./theme";
@@ -94,6 +95,7 @@ async function bootstrap() {
}>
+
diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts
index 62040176..ef92fef9 100644
--- a/frontend/src/utils/request.ts
+++ b/frontend/src/utils/request.ts
@@ -2,9 +2,6 @@ import {message} from "antd";
import Loading from "./loading";
import {errorConfigStore} from "@/utils/errorConfigStore.ts";
import i18n from "@/i18n";
-import i18n from "@/i18n";
-import i18n from "@/i18n";
-import i18n from "@/i18n";
/**
* 通用请求工具类
@@ -526,11 +523,9 @@ request.addRequestInterceptor((config) => {
try {
const sessionData = JSON.parse(session);
if (sessionData.token) {
- // 后端使用 "User" 请求头而不是 "Authorization"
- // 可以直接发送 token 或 username
config.headers = {
...config.headers,
- 'User': sessionData.token, // 使用 User 请求头
+ 'Authorization': `Bearer ${sessionData.token}`,
};
}
} catch (e) {
@@ -550,24 +545,29 @@ request.addRequestInterceptor((config) => {
// --- 常量配置 ---
const DEFAULT_ERROR_MSG = '系统繁忙,请稍后重试';
// 需要触发重新登录的 Code 集合 (包含 HTTP 401 和 业务 Token 过期码)
-const AUTH_ERR_CODES = [401, '401', 'common.401'];
+// 注意:后端返回的是 "common.0401"(有前导零)
+const AUTH_ERR_CODES = [401, '401', 'common.401', 'common.0401'];
// --- 辅助函数:防抖处理登录失效 ---
let isRelogging = false;
+
const handleLoginRedirect = () => {
- if (isRelogging) return;
+ console.log('[Auth] handleLoginRedirect called, isRelogging:', isRelogging);
+
+ if (isRelogging) {
+ console.log('[Auth] Skipping - already relogging');
+ return;
+ }
isRelogging = true;
- // 1. 清除 Session / Token
localStorage.removeItem('session');
- // 2. 触发登录弹窗事件 (根据你的架构,这里可以是 dispatch event 或 router 跳转)
- const loginEvent = new CustomEvent('show-login');
- window.dispatchEvent(loginEvent);
+ console.log('[Auth] Dispatching show-login event');
+ window.dispatchEvent(new CustomEvent('show-login'));
- // 3. 重置标志位 (3秒后才允许再次触发)
setTimeout(() => {
isRelogging = false;
+ console.log('[Auth] Reset isRelogging flag');
}, 3000);
};
@@ -578,6 +578,7 @@ request.addResponseInterceptor(async (response, config) => {
}
const { status } = response;
+ console.log('[API Interceptor] Response status:', status, 'URL:', config?.url);
// ------------------ 修改重点开始 ------------------
@@ -588,10 +589,11 @@ request.addResponseInterceptor(async (response, config) => {
// 关键点 2: 必须用 .clone(),因为流只能读一次。读了克隆的,原版 response 还能留给外面用
// 关键点 3: 必须 await,因为读取流是异步的
resData = await response.clone().json();
+ console.log('[API Interceptor] Response data:', resData);
} catch (e) {
// 如果后端返回的不是 JSON (比如 404 HTML 页面,或者空字符串),json() 会报错
// 这里捕获异常,保证 resData 至少是个空对象,不会导致后面取值 crash
- console.warn('响应体不是有效的JSON:', e);
+ console.warn('[API Interceptor] 响应体不是有效的JSON:', e);
resData = {};
}
@@ -599,6 +601,7 @@ request.addResponseInterceptor(async (response, config) => {
// 优先取后端 body 里的 business code,没有则取 HTTP status
const code = resData.code ?? status;
const codeStr = String(code);
+ console.log('[API Interceptor] Extracted code:', code, 'codeStr:', codeStr);
// 3. 判断成功 (根据你的后端约定:200/0 为成功)
// 如果是成功状态,直接返回 response,不拦截
@@ -623,7 +626,9 @@ request.addResponseInterceptor(async (response, config) => {
}
// 7. 处理 Token 过期 / 未登录
- if (AUTH_ERR_CODES.includes(code) || AUTH_ERR_CODES.includes(codeStr)) {
+ const isAuthError = AUTH_ERR_CODES.includes(code) || AUTH_ERR_CODES.includes(codeStr);
+ console.log('[API Interceptor] Is auth error?', isAuthError, 'AUTH_ERR_CODES:', AUTH_ERR_CODES);
+ if (isAuthError) {
handleLoginRedirect();
}