From f083e8962729414eb7e482d1cc3ea1c68ba0a633 Mon Sep 17 00:00:00 2001 From: hhhhsc <1710496817@qq.com> Date: Mon, 30 Mar 2026 20:29:11 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/main.tsx | 34 ++++++++++++++++++++++++++++ frontend/src/pages/Layout/Header.tsx | 18 +++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 98982f6d..7c738e17 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -43,10 +43,44 @@ function showLoadingUI() { `; } +/** + * 自定义首页URL重定向 + * 在任何渲染之前检查系统参数 sys.home.page.url,若已配置则立即跳转,确保无闪烁。 + * 使用原始 fetch 避免触发 antd message 等尚未初始化的 UI 组件。 + */ +async function checkHomePageRedirect(): Promise { + if (window.location.pathname !== '/') { + return false; + } + try { + const response = await fetch('/api/sys-param/sys.home.page.url', { + method: 'GET', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + }); + if (response.ok) { + const result = await response.json(); + const url = result?.data?.paramValue?.trim(); + if (url) { + window.location.replace(url); + return true; + } + } + } catch { + // 忽略错误,继续正常启动 + } + return false; +} + async function bootstrap() { const container = document.getElementById("root"); if (!container) return; + // 在任何 UI 渲染之前检查自定义首页重定向 + if (await checkHomePageRedirect()) { + return; + } + showLoadingUI(); try { diff --git a/frontend/src/pages/Layout/Header.tsx b/frontend/src/pages/Layout/Header.tsx index 37359fb0..1d26d584 100644 --- a/frontend/src/pages/Layout/Header.tsx +++ b/frontend/src/pages/Layout/Header.tsx @@ -1,11 +1,12 @@ import { User, Globe, LogIn, UserPlus, Sparkles, Shield } from "lucide-react" -import { memo, useState, useEffect } from "react"; +import { memo, useState, useEffect, useCallback, useRef } 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 { getHomePageUrl } from "@/utils/systemParam"; import { useTranslation } from "react-i18next"; import i18n from "@/i18n"; @@ -41,6 +42,7 @@ export function Header() { const [currentUser, setCurrentUser] = useState(null); const [authMode, setAuthMode] = useState<'SSO' | 'JWT' | 'NONE'>('NONE'); const [userLoading, setUserLoading] = useState(true); + const [homePageUrl, setHomePageUrl] = useState(null); const handleLogin = async (values: { username: string; password: string }) => { try { @@ -121,6 +123,18 @@ export function Header() { setSignupOpen(true); }; + const handleHomeClick = useCallback((e: React.MouseEvent) => { + if (homePageUrl) { + e.preventDefault(); + window.location.href = homePageUrl; + } + }, [homePageUrl]); + + // 获取自定义首页URL + useEffect(() => { + getHomePageUrl().then(setHomePageUrl); + }, []); + // 检测是否在 ME 环境 const isSSOAvailable = () => { const hostname = window.location.hostname; @@ -269,7 +283,7 @@ export function Header() {
- +
From 519728d008f174480790102066be1a7ae2d4dff0 Mon Sep 17 00:00:00 2001 From: hhhhsc <1710496817@qq.com> Date: Tue, 31 Mar 2026 09:46:05 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/main.tsx | 64 ++++++++++++++++++++++++---- frontend/src/pages/Layout/Header.tsx | 15 ++++--- frontend/src/utils/systemParam.ts | 22 +++++++++- 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7c738e17..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,33 +44,72 @@ 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 避免触发 antd message 等尚未初始化的 UI 组件。 + * 使用原始 fetch 但携带 JWT token,避免已登录用户仍收到 401。 */ -async function checkHomePageRedirect(): Promise { +async function checkHomePageRedirect(): Promise<{ redirected: boolean; authNeeded: boolean }> { if (window.location.pathname !== '/') { - return false; + 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: { 'Content-Type': 'application/json' }, + headers, }); if (response.ok) { const result = await response.json(); const url = result?.data?.paramValue?.trim(); if (url) { + setCachedHomePageUrl(url); window.location.replace(url); - return true; + 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 false; + return { redirected: false, authNeeded: false }; } async function bootstrap() { @@ -77,7 +117,8 @@ async function bootstrap() { if (!container) return; // 在任何 UI 渲染之前检查自定义首页重定向 - if (await checkHomePageRedirect()) { + const { redirected, authNeeded } = await checkHomePageRedirect(); + if (redirected) { return; } @@ -106,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 1d26d584..9271977d 100644 --- a/frontend/src/pages/Layout/Header.tsx +++ b/frontend/src/pages/Layout/Header.tsx @@ -1,11 +1,12 @@ import { User, Globe, LogIn, UserPlus, Sparkles, Shield } from "lucide-react" -import { memo, useState, useEffect, useCallback, useRef } 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"; @@ -42,7 +43,6 @@ export function Header() { const [currentUser, setCurrentUser] = useState(null); const [authMode, setAuthMode] = useState<'SSO' | 'JWT' | 'NONE'>('NONE'); const [userLoading, setUserLoading] = useState(true); - const [homePageUrl, setHomePageUrl] = useState(null); const handleLogin = async (values: { username: string; password: string }) => { try { @@ -124,15 +124,16 @@ export function Header() { }; const handleHomeClick = useCallback((e: React.MouseEvent) => { - if (homePageUrl) { + const homeUrl = getCachedHomePageUrl(); + if (homeUrl) { e.preventDefault(); - window.location.href = homePageUrl; + window.location.href = homeUrl; } - }, [homePageUrl]); + }, []); - // 获取自定义首页URL + // 已登录时后台刷新缓存,保持与后端同步 useEffect(() => { - getHomePageUrl().then(setHomePageUrl); + getHomePageUrl().then(url => setCachedHomePageUrl(url)).catch(() => {}); }, []); // 检测是否在 ME 环境 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 {