You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
6.6 KiB
234 lines
6.6 KiB
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
|
|
import type { User, MenuItem, UserOrgInfo } from '@/types'
|
|
import { apiClient } from '@/services/api'
|
|
|
|
interface AuthContextType {
|
|
user: User | null
|
|
token: string | null
|
|
isAuthenticated: boolean
|
|
isLoading: boolean
|
|
login: (account: string, password: string) => Promise<void>
|
|
loginWithToken: (token: string) => void
|
|
logout: () => void
|
|
currentOrg: { id: number; name: string } | null
|
|
userOrgs: UserOrgInfo[]
|
|
userMenus: MenuItem[]
|
|
switchOrg: (orgId: number) => Promise<void>
|
|
refreshMenus: () => Promise<void>
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null)
|
|
const [token, setToken] = useState<string | null>(null)
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [currentOrg, setCurrentOrg] = useState<{ id: number; name: string } | null>(null)
|
|
const [userOrgs, setUserOrgs] = useState<UserOrgInfo[]>([])
|
|
const [userMenus, setUserMenus] = useState<MenuItem[]>([])
|
|
|
|
useEffect(() => {
|
|
const storedToken = localStorage.getItem('token')
|
|
const storedUser = localStorage.getItem('user')
|
|
|
|
if (storedToken) {
|
|
setToken(storedToken)
|
|
}
|
|
|
|
if (storedUser) {
|
|
try {
|
|
setUser(JSON.parse(storedUser))
|
|
} catch {
|
|
localStorage.removeItem('user')
|
|
}
|
|
}
|
|
|
|
const savedOrg = localStorage.getItem('currentOrg')
|
|
if (savedOrg) {
|
|
try { setCurrentOrg(JSON.parse(savedOrg)) } catch {}
|
|
}
|
|
|
|
setIsLoading(false)
|
|
}, [])
|
|
|
|
const refreshMenus = async () => {
|
|
try {
|
|
const data = await apiClient.getCurrentMenus()
|
|
setUserMenus(data.list || [])
|
|
} catch (e) {
|
|
console.error('Failed to fetch menus:', e)
|
|
}
|
|
}
|
|
|
|
const loadUserContext = async () => {
|
|
try {
|
|
const orgsData = await apiClient.getUserOrgs()
|
|
const orgs = orgsData.list || []
|
|
setUserOrgs(orgs)
|
|
|
|
if (orgs.length > 0) {
|
|
const storedOrg = localStorage.getItem('currentOrg')
|
|
let selectedOrg = orgs[0]
|
|
if (storedOrg) {
|
|
try {
|
|
const parsed = JSON.parse(storedOrg)
|
|
const found = orgs.find((o: UserOrgInfo) => o.orgId === parsed.id)
|
|
if (found) selectedOrg = found
|
|
} catch {}
|
|
}
|
|
setCurrentOrg({ id: selectedOrg.orgId, name: selectedOrg.orgName })
|
|
localStorage.setItem('currentOrg', JSON.stringify({ id: selectedOrg.orgId, name: selectedOrg.orgName }))
|
|
}
|
|
|
|
await refreshMenus()
|
|
} catch (e) {
|
|
console.error('Failed to load user context:', e)
|
|
await refreshMenus()
|
|
}
|
|
}
|
|
|
|
const switchOrgFn = async (orgId: number) => {
|
|
try {
|
|
const data = await apiClient.switchOrg(orgId)
|
|
if (data.token) {
|
|
localStorage.setItem('token', data.token)
|
|
setToken(data.token)
|
|
|
|
try {
|
|
const payload = JSON.parse(atob(data.token.split('.')[1]))
|
|
const userData: User = {
|
|
id: payload.userId || 0,
|
|
username: payload.username || '',
|
|
email: '',
|
|
role: payload.role || 'user',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
setUser(userData)
|
|
localStorage.setItem('user', JSON.stringify(userData))
|
|
} catch {}
|
|
|
|
const org = userOrgs.find((o: UserOrgInfo) => o.orgId === orgId)
|
|
if (org) {
|
|
setCurrentOrg({ id: org.orgId, name: org.orgName })
|
|
localStorage.setItem('currentOrg', JSON.stringify({ id: org.orgId, name: org.orgName }))
|
|
}
|
|
|
|
await refreshMenus()
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to switch org:', e)
|
|
throw e
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (token && !isLoading) {
|
|
loadUserContext()
|
|
}
|
|
}, [token, isLoading])
|
|
|
|
const login = async (account: string, password: string) => {
|
|
try {
|
|
const response = await apiClient.login({ account, password })
|
|
|
|
if (response.success && response.token) {
|
|
setToken(response.token)
|
|
|
|
// 解析 JWT 获取用户信息
|
|
try {
|
|
const payload = JSON.parse(atob(response.token.split('.')[1]))
|
|
const userData: User = {
|
|
id: payload.userId || 0,
|
|
username: payload.username || account,
|
|
email: '',
|
|
role: payload.role || 'user',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
setUser(userData)
|
|
localStorage.setItem('user', JSON.stringify(userData))
|
|
} catch {
|
|
const userData: User = {
|
|
id: 0,
|
|
username: account,
|
|
email: '',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
setUser(userData)
|
|
localStorage.setItem('user', JSON.stringify(userData))
|
|
}
|
|
}
|
|
} catch (error) {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
const loginWithToken = (ssoToken: string) => {
|
|
localStorage.setItem('token', ssoToken)
|
|
setToken(ssoToken)
|
|
|
|
// 解析 JWT 获取用户信息
|
|
try {
|
|
const payload = JSON.parse(atob(ssoToken.split('.')[1]))
|
|
const userData: User = {
|
|
id: payload.userId || 0,
|
|
username: payload.username || '',
|
|
email: payload.email || '',
|
|
role: payload.role || 'user',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
setUser(userData)
|
|
localStorage.setItem('user', JSON.stringify(userData))
|
|
} catch {
|
|
// JWT 解析失败,使用占位数据
|
|
const userData: User = {
|
|
id: 0,
|
|
username: 'SSO User',
|
|
email: '',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
setUser(userData)
|
|
localStorage.setItem('user', JSON.stringify(userData))
|
|
}
|
|
}
|
|
|
|
const logout = () => {
|
|
apiClient.logout()
|
|
setToken(null)
|
|
setUser(null)
|
|
setCurrentOrg(null)
|
|
setUserOrgs([])
|
|
setUserMenus([])
|
|
localStorage.removeItem('user')
|
|
localStorage.removeItem('currentOrg')
|
|
}
|
|
|
|
const value: AuthContextType = {
|
|
user,
|
|
token,
|
|
isAuthenticated: !!token,
|
|
isLoading,
|
|
login,
|
|
loginWithToken,
|
|
logout,
|
|
currentOrg,
|
|
userOrgs,
|
|
userMenus,
|
|
switchOrg: switchOrgFn,
|
|
refreshMenus,
|
|
}
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext)
|
|
if (context === undefined) {
|
|
throw new Error('useAuth must be used within an AuthProvider')
|
|
}
|
|
return context
|
|
}
|
|
|