202 lines
6.2 KiB
TypeScript
202 lines
6.2 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
||
import { useSelector, useDispatch } from 'react-redux'
|
||
import { Spin } from 'antd'
|
||
import type { AppDispatch } from '../store/store'
|
||
import {
|
||
login,
|
||
verifyToken,
|
||
restoreAuthState,
|
||
clearError,
|
||
clearAuthState,
|
||
selectIsAuthenticated,
|
||
selectAuthLoading,
|
||
selectAuthError,
|
||
selectToken
|
||
} from '../store/slices/authSlice'
|
||
import LoginPage from './LoginPage'
|
||
import InstallPage from './InstallPage'
|
||
import apiClient from '../services/apiClient'
|
||
|
||
interface AuthGuardProps {
|
||
children: React.ReactNode
|
||
}
|
||
|
||
/**
|
||
* 认证守卫组件
|
||
* 负责验证用户登录状态,未登录时显示登录页面
|
||
*/
|
||
const AuthGuard: React.FC<AuthGuardProps> = ({ children }) => {
|
||
const dispatch = useDispatch<AppDispatch>()
|
||
const isAuthenticated = useSelector(selectIsAuthenticated)
|
||
const authLoading = useSelector(selectAuthLoading)
|
||
const authError = useSelector(selectAuthError)
|
||
const token = useSelector(selectToken)
|
||
|
||
const [isInitializing, setIsInitializing] = useState(true)
|
||
const [loginError, setLoginError] = useState<string | null>(null)
|
||
const [systemInitialized, setSystemInitialized] = useState<boolean | null>(null)
|
||
|
||
// 调试:监听认证状态变化
|
||
useEffect(() => {
|
||
console.log('🔐 AuthGuard - 认证状态变化:', {
|
||
isAuthenticated,
|
||
authLoading,
|
||
token: token ? '***' : null,
|
||
isInitializing,
|
||
systemInitialized
|
||
})
|
||
}, [isAuthenticated, authLoading, token, isInitializing, systemInitialized])
|
||
|
||
// 组件挂载时检查系统初始化状态
|
||
useEffect(() => {
|
||
const initializeAuth = async () => {
|
||
try {
|
||
console.log('🔐 检查系统初始化状态...')
|
||
|
||
// 首先检查系统是否已初始化
|
||
// const initResult = await apiClient.get<any>('/')
|
||
const initResult = await apiClient.get<any>('/api/auth/check-initialization')
|
||
|
||
if (initResult.success) {
|
||
setSystemInitialized(initResult.isInitialized)
|
||
console.log(`🔐 系统初始化状态: ${initResult.isInitialized ? '已初始化' : '未初始化'}`)
|
||
|
||
// 如果系统已初始化,继续认证流程
|
||
if (initResult.isInitialized) {
|
||
console.log('🔐 初始化认证状态...')
|
||
|
||
// 先尝试从本地存储恢复状态
|
||
dispatch(restoreAuthState())
|
||
|
||
// 等待一个tick让状态更新
|
||
await new Promise(resolve => setTimeout(resolve, 0))
|
||
|
||
// 获取恢复后的token
|
||
const currentToken = localStorage.getItem('auth_token')
|
||
|
||
if (currentToken) {
|
||
console.log('🔐 找到本地token,验证有效性...')
|
||
// 验证token是否仍然有效
|
||
try {
|
||
const result = await dispatch(verifyToken(currentToken))
|
||
|
||
if (verifyToken.fulfilled.match(result)) {
|
||
console.log('✅ Token验证成功')
|
||
} else {
|
||
console.log('❌ Token验证失败:', result.payload)
|
||
setLoginError('登录已过期,请重新登录')
|
||
}
|
||
} catch (error) {
|
||
console.log('❌ Token验证出错:', error)
|
||
setLoginError('登录验证失败,请重新登录')
|
||
}
|
||
} else {
|
||
console.log('🔐 未找到本地token')
|
||
}
|
||
}
|
||
} else {
|
||
console.error('检查系统初始化状态失败')
|
||
setSystemInitialized(false) // 默认为未初始化
|
||
}
|
||
} catch (error) {
|
||
console.error('初始化检查失败:', error)
|
||
setSystemInitialized(false) // 出错时默认为未初始化
|
||
} finally {
|
||
setIsInitializing(false)
|
||
}
|
||
}
|
||
|
||
initializeAuth()
|
||
}, [dispatch])
|
||
|
||
// 监听token过期事件
|
||
useEffect(() => {
|
||
const handleTokenExpired = () => {
|
||
console.log('🔐 Token过期,清除认证状态')
|
||
dispatch(clearAuthState())
|
||
setLoginError('登录已过期,请重新登录')
|
||
}
|
||
|
||
window.addEventListener('auth:token-expired', handleTokenExpired)
|
||
|
||
return () => {
|
||
window.removeEventListener('auth:token-expired', handleTokenExpired)
|
||
}
|
||
}, [dispatch])
|
||
|
||
// 处理登录
|
||
const handleLogin = async (username: string, password: string) => {
|
||
try {
|
||
console.log('🔐 尝试登录:', username)
|
||
setLoginError(null)
|
||
dispatch(clearError())
|
||
|
||
const result = await dispatch(login({ username, password }))
|
||
|
||
if (login.fulfilled.match(result)) {
|
||
console.log('✅ 登录成功')
|
||
setLoginError(null)
|
||
} else if (login.rejected.match(result)) {
|
||
const errorMessage = result.payload || '登录失败'
|
||
console.log('❌ 登录失败:', errorMessage)
|
||
setLoginError(errorMessage)
|
||
throw new Error(errorMessage)
|
||
}
|
||
} catch (error: any) {
|
||
console.error('登录过程出错:', error)
|
||
throw error // 重新抛出错误,让LoginPage显示加载状态
|
||
}
|
||
}
|
||
|
||
// 清除登录错误
|
||
useEffect(() => {
|
||
if (authError && authError !== loginError) {
|
||
setLoginError(authError)
|
||
}
|
||
}, [authError, loginError])
|
||
|
||
// 处理安装完成
|
||
const handleInstallComplete = () => {
|
||
console.log('🔐 安装完成,刷新初始化状态')
|
||
setSystemInitialized(true)
|
||
setLoginError(null)
|
||
}
|
||
|
||
// 初始化加载中
|
||
if (isInitializing || systemInitialized === null) {
|
||
return (
|
||
<div style={{
|
||
minHeight: '100vh',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
||
}}>
|
||
<Spin size="large" style={{ color: 'white' }} />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 系统未初始化,显示安装页面
|
||
if (!systemInitialized) {
|
||
return (
|
||
<InstallPage onInstallComplete={handleInstallComplete} />
|
||
)
|
||
}
|
||
|
||
// 系统已初始化但未登录,显示登录页面
|
||
if (!isAuthenticated) {
|
||
return (
|
||
<LoginPage
|
||
onLogin={handleLogin}
|
||
loading={authLoading}
|
||
error={loginError || undefined}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// 已登录,显示主应用
|
||
return <>{children}</>
|
||
}
|
||
|
||
export default AuthGuard
|