Files
TERES_web_frontend/src/hooks/login_hooks.ts
guangfei.zhao 650c41dc41 refactor(layout): migrate styled components to sx prop in Header and Sidebar
feat(pages): add new KnowledgeBaseList and Login pages with complete functionality

feat(services): implement knowledge service with comprehensive API methods

feat(hooks): add login hooks for authentication and form management
2025-10-11 11:31:24 +08:00

433 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect, useMemo } from 'react';
import { useNavigate, useSearchParams, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
import userService from '../services/user_service';
import { rsaPsw } from '../utils/encryption';
/**
* OAuth回调处理Hook
*/
export const useOAuthCallback = () => {
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const error = searchParams.get('error');
const newSearchParams: URLSearchParams = useMemo(
() => new URLSearchParams(searchParams.toString()),
[searchParams],
);
useEffect(() => {
if (error) {
// 显示错误信息(这里可以集成你的消息提示组件)
console.error('OAuth Error:', error);
setTimeout(() => {
navigate('/login');
newSearchParams.delete('error');
setSearchParams(newSearchParams);
}, 1000);
return;
}
const auth = searchParams.get('auth');
if (auth) {
// 存储认证信息
localStorage.setItem('token', auth);
newSearchParams.delete('auth');
setSearchParams(newSearchParams);
navigate('/');
}
}, [
error,
searchParams,
newSearchParams,
navigate,
setSearchParams,
]);
return searchParams.get('auth');
};
/**
* 认证状态管理Hook
*/
export const useAuth = () => {
const navigate = useNavigate();
const location = useLocation();
const [searchParams] = useSearchParams();
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
const [userInfo, setUserInfo] = useState<any>(null);
const [token, setToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
// OAuth回调处理
const auth = useOAuthCallback();
// 检查认证状态
const checkAuthStatus = () => {
try {
const storedToken = localStorage.getItem('token');
const storedUserInfo = localStorage.getItem('userInfo');
if (storedToken) {
setToken(storedToken);
setIsAuthenticated(true);
if (storedUserInfo) {
setUserInfo(JSON.parse(storedUserInfo));
}
} else {
setToken(null);
setIsAuthenticated(false);
setUserInfo(null);
}
} catch (error) {
console.error('Error checking auth status:', error);
setToken(null);
setIsAuthenticated(false);
setUserInfo(null);
} finally {
setIsLoading(false);
}
};
// 登出功能
const logout = () => {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
setToken(null);
setIsAuthenticated(false);
setUserInfo(null);
navigate('/login');
};
// 更新认证状态
const updateAuthStatus = (newToken: string, newUserInfo: any) => {
setToken(newToken);
setUserInfo(newUserInfo);
setIsAuthenticated(true);
localStorage.setItem('token', newToken);
localStorage.setItem('userInfo', JSON.stringify(newUserInfo));
};
// 带参数重定向到登录页面
const redirectToLogin = (returnUrl?: string) => {
const currentPath = returnUrl || location.pathname + location.search;
if (currentPath !== '/login') {
navigate(`/login?redirect=${encodeURIComponent(currentPath)}`);
} else {
navigate('/login');
}
};
// 重定向到指定页面或首页
const redirectAfterLogin = () => {
const redirectUrl = searchParams.get('redirect');
if (redirectUrl) {
navigate(decodeURIComponent(redirectUrl));
} else {
navigate('/');
}
};
useEffect(() => {
checkAuthStatus();
}, [auth]);
return {
isAuthenticated,
userInfo,
token,
isLoading,
checkAuthStatus,
logout,
updateAuthStatus,
redirectToLogin,
redirectAfterLogin,
};
};
export interface LoginFormData {
email: string;
password: string;
rememberEmail?: boolean;
}
export interface RegisterFormData {
email: string;
password: string;
nickname: string;
}
/**
* 登录Hook
*/
export const useLogin = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [isLoading, setIsLoading] = useState(false);
const login = async (data: LoginFormData) => {
setIsLoading(true);
try {
// RSA加密密码
const encryptedPassword = rsaPsw(data.password);
const response = await userService.login({
email: data.email.trim(),
password: encryptedPassword
});
const { data: res = {} } = response;
const { code, message } = res;
if (code === 0) {
// 登录成功处理token和用户信息
const { data: userData } = res;
const token = userData.access_token;
const userInfo = {
...userData,
avatar: userData.avatar,
name: userData.nickname,
email: userData.email,
};
// 同步更新localStorage
localStorage.setItem('token', token);
localStorage.setItem('userInfo', JSON.stringify(userInfo));
// 直接进行重定向不依赖React状态更新
const redirectUrl = searchParams.get('redirect');
if (redirectUrl) {
navigate(decodeURIComponent(redirectUrl), { replace: true });
} else {
navigate('/', { replace: true });
}
return { success: true };
} else {
// 登录失败
return {
success: false,
error: message || t('login.loginFailed')
};
}
} catch (error: any) {
// 处理网络错误或其他异常
return {
success: false,
error: error.message || t('login.networkError')
};
} finally {
setIsLoading(false);
}
};
return { login, isLoading };
};
/**
* 注册Hook
*/
export const useRegister = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const register = async (data: RegisterFormData) => {
setIsLoading(true);
try {
// RSA加密密码
const encryptedPassword = rsaPsw(data.password);
const response = await userService.register({
nickname: data.nickname,
email: data.email.trim(),
password: encryptedPassword
});
const { data: res = {} } = response;
const { code, message } = res;
if (code === 0) {
// 注册成功
return {
success: true,
email: data.email
};
} else {
// 注册失败
return {
success: false,
error: message || t('login.registerFailed')
};
}
} catch (error: any) {
// 处理网络错误或其他异常
return {
success: false,
error: error.message || t('login.networkError')
};
} finally {
setIsLoading(false);
}
};
return { register, isLoading };
};
/**
* 登录表单管理Hook
*/
export const useLoginForm = () => {
const { t } = useTranslation();
const [tabValue, setTabValue] = useState(0); // 0: 登录, 1: 注册
const [error, setError] = useState('');
// 登录表单
const loginForm = useForm<LoginFormData>({
defaultValues: {
email: '',
password: '',
rememberEmail: false
}
});
// 注册表单
const registerForm = useForm<RegisterFormData>({
defaultValues: {
email: '',
password: '',
nickname: ''
}
});
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
setError('');
// 清空表单错误
loginForm.clearErrors();
registerForm.clearErrors();
};
const switchToLogin = (email?: string) => {
setTabValue(0);
setError('');
if (email) {
loginForm.setValue('email', email);
}
};
const setFormError = (errorMessage: string) => {
setError(errorMessage);
};
const clearError = () => {
setError('');
};
return {
// 表单状态
tabValue,
setTabValue,
error,
loginForm,
registerForm,
// 表单操作
handleTabChange,
switchToLogin,
setFormError,
clearError,
// 表单验证规则
loginValidation: {
email: {
required: t('login.emailPlaceholder'),
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: t('login.emailInvalid')
}
},
password: {
required: t('login.passwordPlaceholder'),
minLength: {
value: 6,
message: t('login.passwordMinLength')
}
}
},
registerValidation: {
nickname: {
required: t('login.nicknamePlaceholder'),
minLength: {
value: 2,
message: t('login.nicknameMinLength')
}
},
email: {
required: t('login.emailPlaceholder'),
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: t('login.emailInvalid')
}
},
password: {
required: t('login.passwordPlaceholder'),
minLength: {
value: 8,
message: t('login.passwordMinLength')
}
}
}
};
};
/**
* 登录页面主Hook - 整合所有登录相关逻辑
*/
export const useLoginPage = () => {
const loginHook = useLogin();
const registerHook = useRegister();
const formHook = useLoginForm();
const handleLogin = async (data: LoginFormData) => {
formHook.clearError();
const result = await loginHook.login(data);
if (!result.success && result.error) {
formHook.setFormError(result.error);
}
return result;
};
const handleRegister = async (data: RegisterFormData) => {
formHook.clearError();
const result = await registerHook.register(data);
if (result.success) {
// 注册成功,切换到登录表单并填入邮箱
formHook.registerForm.reset();
formHook.switchToLogin(result.email);
} else if (result.error) {
formHook.setFormError(result.error);
}
return result;
};
const isSubmitting = loginHook.isLoading || registerHook.isLoading;
return {
// 表单相关
...formHook,
// 提交处理
handleLogin,
handleRegister,
isSubmitting,
};
};