2026-09-03 15:39:10 +08:00
|
|
|
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
|
|
|
|
import { authLogin, authLogout, AUTH_EXPIRED_EVENT, clearSession, getUserName, setSession, TOKEN_KEY, USER_KEY } from '../api/client'
|
2026-09-01 13:13:40 +08:00
|
|
|
|
|
|
|
|
interface AuthState {
|
|
|
|
|
token: string
|
|
|
|
|
username: string
|
|
|
|
|
login: (u: string, p: string) => Promise<void>
|
|
|
|
|
logout: () => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Ctx = createContext<AuthState>({ token: '', username: '', login: async () => {}, logout: () => {} })
|
|
|
|
|
|
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
2026-09-03 15:39:10 +08:00
|
|
|
const [token, setToken] = useState(() => localStorage.getItem(TOKEN_KEY) || '')
|
|
|
|
|
const [username, setUsername] = useState(() => localStorage.getItem(USER_KEY) || '')
|
2026-09-01 13:13:40 +08:00
|
|
|
const login = async (u: string, p: string) => {
|
|
|
|
|
const data = await authLogin(u, p)
|
|
|
|
|
setSession(data.token, data.username)
|
|
|
|
|
setToken(data.token)
|
|
|
|
|
setUsername(data.username)
|
|
|
|
|
}
|
|
|
|
|
const logout = () => {
|
2026-09-03 15:39:10 +08:00
|
|
|
// 先吊销服务端令牌(fire-and-forget,失败不阻塞退出),再清理本地
|
|
|
|
|
authLogout().catch(() => {})
|
2026-09-01 13:13:40 +08:00
|
|
|
clearSession()
|
|
|
|
|
setToken('')
|
|
|
|
|
setUsername('')
|
|
|
|
|
}
|
2026-09-03 15:39:10 +08:00
|
|
|
|
|
|
|
|
// 任何请求命中 401(登录态无效/过期)时,同步清空内存态,
|
|
|
|
|
// AppLayout 会立即自动跳转到登录页。
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const onExpired = () => {
|
|
|
|
|
clearSession()
|
|
|
|
|
setToken('')
|
|
|
|
|
setUsername('')
|
|
|
|
|
}
|
|
|
|
|
window.addEventListener(AUTH_EXPIRED_EVENT, onExpired)
|
|
|
|
|
return () => window.removeEventListener(AUTH_EXPIRED_EVENT, onExpired)
|
|
|
|
|
}, [])
|
|
|
|
|
|
2026-09-01 13:13:40 +08:00
|
|
|
return <Ctx.Provider value={{ token, username, login, logout }}>{children}</Ctx.Provider>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const useAuth = () => useContext(Ctx)
|