diff --git a/src/App.tsx b/src/App.tsx index f19291b..4731747 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,17 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { getToken } from "./api"; import { AdminShell } from "@/components/admin/AdminShell"; +import { subscribeAuthExpired } from "@/lib/auth-session"; import { LoginPage } from "@/pages/login/LoginPage"; export default function App() { const [authed, setAuthed] = useState(!!getToken()); + useEffect(() => { + return subscribeAuthExpired(() => setAuthed(false)); + }, []); + if (!authed) { return setAuthed(true)} />; } diff --git a/src/api.ts b/src/api.ts index 566719e..2a30ee5 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,3 +1,5 @@ +import { notifyAuthExpired, shouldExpireAdminSession } from "@/lib/auth-session"; + export type Dashboard = { stats: { productCount: number; @@ -319,6 +321,11 @@ export function clearToken() { window.localStorage.removeItem(TOKEN_KEY); } +function handleAuthExpired() { + clearToken(); + notifyAuthExpired(); +} + async function request(path: string, options: RequestInit = {}): Promise { const headers = new Headers(options.headers); headers.set("Content-Type", "application/json"); @@ -331,6 +338,9 @@ async function request(path: string, options: RequestInit = {}): Promise { }); if (!response.ok) { + if (path !== "/api/admin/auth/login" && shouldExpireAdminSession(response.status, Boolean(token))) { + handleAuthExpired(); + } const body = await response.json().catch(() => ({})); throw new Error(body.message ?? `请求失败:${response.status}`); } @@ -387,6 +397,9 @@ export async function uploadMediaAsset(file: File, group = "site-config") { }); if (!response.ok) { + if (shouldExpireAdminSession(response.status, Boolean(token))) { + handleAuthExpired(); + } const body = await response.json().catch(() => ({})); throw new Error(body.message ?? `请求失败:${response.status}`); } diff --git a/src/lib/auth-session.ts b/src/lib/auth-session.ts new file mode 100644 index 0000000..cbc274c --- /dev/null +++ b/src/lib/auth-session.ts @@ -0,0 +1,14 @@ +export const AUTH_EXPIRED_EVENT = "wonderq-admin-auth-expired"; + +export function shouldExpireAdminSession(status: number, hadToken: boolean) { + return hadToken && status === 401; +} + +export function notifyAuthExpired() { + window.dispatchEvent(new Event(AUTH_EXPIRED_EVENT)); +} + +export function subscribeAuthExpired(callback: () => void) { + window.addEventListener(AUTH_EXPIRED_EVENT, callback); + return () => window.removeEventListener(AUTH_EXPIRED_EVENT, callback); +} diff --git a/src/pages/structure/StructurePage.tsx b/src/pages/structure/StructurePage.tsx index 8f18d51..764cdb8 100644 --- a/src/pages/structure/StructurePage.tsx +++ b/src/pages/structure/StructurePage.tsx @@ -3,7 +3,6 @@ import { ArrowDown, ArrowUp, ChevronRight, - Eye, Plus, RefreshCcw, Save, @@ -666,22 +665,8 @@ export function StructurePage({ ))} - ) : ( -
- 当前维护页面 -

{activePage.title}

-

{activePage.frontPath}

- {activePage.subtitle} -
- )} + ) : null}
-
- - - {activePage.frontPath} - {activePage.subtitle} - -

页面模块

{activePage.modules.map((module, index) => (
{ + assert.equal(shouldExpireAdminSession(401, true), true); + assert.equal(shouldExpireAdminSession(401, false), false); + assert.equal(shouldExpireAdminSession(403, true), false); + assert.equal(shouldExpireAdminSession(500, true), false); +});