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
This commit is contained in:
2025-10-11 11:31:24 +08:00
parent d6ff547f20
commit 650c41dc41
7 changed files with 450 additions and 125 deletions

433
src/hooks/login_hooks.ts Normal file
View File

@@ -0,0 +1,433 @@
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,
};
};