diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 98982f6d..06485805 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -10,6 +10,7 @@ import { store } from "./store"; import { Provider } from "react-redux"; import theme from "./theme"; import {errorConfigStore} from "@/utils/errorConfigStore.ts"; +import { setCachedHomePageUrl, getCachedHomePageUrl } from "@/utils/systemParam"; import "@/i18n"; function showLoadingUI() { @@ -43,10 +44,84 @@ function showLoadingUI() { `; } +/** + * 从 localStorage 读取 JWT token + */ +function getAuthToken(): string | null { + const session = localStorage.getItem('session'); + if (session) { + try { + return JSON.parse(session).token || null; + } catch { + return null; + } + } + return null; +} + +/** + * 自定义首页URL重定向 + * 在任何渲染之前检查系统参数 sys.home.page.url,若已配置则立即跳转,确保无闪烁。 + * 使用原始 fetch 但携带 JWT token,避免已登录用户仍收到 401。 + */ +async function checkHomePageRedirect(): Promise<{ redirected: boolean; authNeeded: boolean }> { + if (window.location.pathname !== '/') { + return { redirected: false, authNeeded: false }; + } + + const headers: Record = { 'Content-Type': 'application/json' }; + const token = getAuthToken(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + try { + const response = await fetch('/api/sys-param/sys.home.page.url', { + method: 'GET', + credentials: 'include', + headers, + }); + if (response.ok) { + const result = await response.json(); + const url = result?.data?.paramValue?.trim(); + if (url) { + setCachedHomePageUrl(url); + window.location.replace(url); + return { redirected: true, authNeeded: false }; + } + // 参数存在但值为空 → 管理员已清除,清掉缓存 + setCachedHomePageUrl(null); + } else if (response.status === 401) { + // 未登录,尝试从缓存读取 + const cachedUrl = getCachedHomePageUrl(); + if (cachedUrl) { + window.location.replace(cachedUrl); + return { redirected: true, authNeeded: false }; + } + // 未登录且无缓存,需要弹出登录框 + return { redirected: false, authNeeded: true }; + } + } catch { + // 网络错误等,尝试从缓存读取 + const cachedUrl = getCachedHomePageUrl(); + if (cachedUrl) { + window.location.replace(cachedUrl); + return { redirected: true, authNeeded: false }; + } + } + return { redirected: false, authNeeded: false }; +} + async function bootstrap() { const container = document.getElementById("root"); if (!container) return; + // 在任何 UI 渲染之前检查自定义首页重定向 + const { redirected, authNeeded } = await checkHomePageRedirect(); + if (redirected) { + return; + } + showLoadingUI(); try { @@ -72,6 +147,13 @@ async function bootstrap() { ); + + // 未登录且无缓存时,等 React 挂载后弹出登录框 + if (authNeeded) { + setTimeout(() => { + window.dispatchEvent(new CustomEvent('show-login')); + }, 500); + } } bootstrap(); diff --git a/frontend/src/pages/Layout/Header.tsx b/frontend/src/pages/Layout/Header.tsx index 37359fb0..9271977d 100644 --- a/frontend/src/pages/Layout/Header.tsx +++ b/frontend/src/pages/Layout/Header.tsx @@ -1,11 +1,13 @@ import { User, Globe, LogIn, UserPlus, Sparkles, Shield } from "lucide-react" -import { memo, useState, useEffect } from "react"; +import { memo, useState, useEffect, useCallback } from "react"; import { NavLink } from "react-router"; import { Button, Dropdown, message } from "antd" import type { MenuProps } from 'antd' import { LoginDialog } from "./LoginDialog" import { SignupDialog } from "./SignupDialog" import { post, get } from "@/utils/request.ts"; +import { getCachedHomePageUrl, setCachedHomePageUrl } from "@/utils/systemParam"; +import { getHomePageUrl } from "@/utils/systemParam"; import { useTranslation } from "react-i18next"; import i18n from "@/i18n"; @@ -121,6 +123,19 @@ export function Header() { setSignupOpen(true); }; + const handleHomeClick = useCallback((e: React.MouseEvent) => { + const homeUrl = getCachedHomePageUrl(); + if (homeUrl) { + e.preventDefault(); + window.location.href = homeUrl; + } + }, []); + + // 已登录时后台刷新缓存,保持与后端同步 + useEffect(() => { + getHomePageUrl().then(url => setCachedHomePageUrl(url)).catch(() => {}); + }, []); + // 检测是否在 ME 环境 const isSSOAvailable = () => { const hostname = window.location.hostname; @@ -269,7 +284,7 @@ export function Header() {
- +
diff --git a/frontend/src/utils/systemParam.ts b/frontend/src/utils/systemParam.ts index 44412806..8e95c348 100644 --- a/frontend/src/utils/systemParam.ts +++ b/frontend/src/utils/systemParam.ts @@ -1,7 +1,27 @@ /** - * System Parameter API * 系统参数 API 接口 */ + +// localStorage 缓存 key +const HOME_PAGE_URL_CACHE_KEY = 'datamate:homePageUrl'; + +/** + * 将首页URL写入缓存 + */ +export function setCachedHomePageUrl(url: string | null) { + if (url) { + localStorage.setItem(HOME_PAGE_URL_CACHE_KEY, url); + } else { + localStorage.removeItem(HOME_PAGE_URL_CACHE_KEY); + } +} + +/** + * 同步读取缓存的首页URL + */ +export function getCachedHomePageUrl(): string | null { + return localStorage.getItem(HOME_PAGE_URL_CACHE_KEY); +} import { get } from '@/utils/request'; export interface SysParam {