登陆注册与忘记密码前后端与jwt配置
Change-Id: Ide4ca3ea34609fdb33ea027e28169852fa41784a
diff --git a/rhj/frontend/src/pages/LoginPage/LoginPage.js b/rhj/frontend/src/pages/LoginPage/LoginPage.js
new file mode 100644
index 0000000..c315b7d
--- /dev/null
+++ b/rhj/frontend/src/pages/LoginPage/LoginPage.js
@@ -0,0 +1,380 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { Input, Checkbox, Modal, Alert } from 'antd';
+import { MailOutlined, LockOutlined, ExclamationCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import {
+ getRememberedLoginInfo,
+ saveRememberedLoginInfo,
+ saveAuthInfo,
+ isLoggedIn
+} from '../../utils/auth';
+import { hashPassword } from '../../utils/crypto';
+import './LoginPage.css';
+
+const baseURL = 'http://10.126.59.25:8082';
+
+const LoginPage = () => {
+ const [formData, setFormData] = useState({
+ email: '',
+ password: ''
+ });
+
+ const [rememberMe, setRememberMe] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [errors, setErrors] = useState({
+ email: '',
+ password: ''
+ });
+ const [errorModal, setErrorModal] = useState({
+ visible: false,
+ title: '',
+ content: ''
+ });
+ const [successAlert, setSuccessAlert] = useState({
+ visible: false,
+ message: ''
+ });
+
+ // 显示错误弹窗
+ const showErrorModal = (title, content) => {
+ setErrorModal({
+ visible: true,
+ title: title,
+ content: content
+ });
+ };
+
+ // 关闭错误弹窗
+ const closeErrorModal = () => {
+ setErrorModal({
+ visible: false,
+ title: '',
+ content: ''
+ });
+ };
+
+ // 显示成功提示
+ const showSuccessAlert = (message) => {
+ setSuccessAlert({
+ visible: true,
+ message: message
+ });
+
+ // 3秒后自动隐藏
+ setTimeout(() => {
+ setSuccessAlert({
+ visible: false,
+ message: ''
+ });
+ }, 3000);
+ };
+
+ // 页面加载时检查是否有记住的登录信息
+ useEffect(() => {
+ // 检查是否已经登录
+ if (isLoggedIn()) {
+ // 如果已经有token,可以选择直接跳转到主页面
+ // window.location.href = '/test-dashboard';
+ console.log('用户已登录');
+ }
+
+ // 获取记住的登录信息
+ const rememberedInfo = getRememberedLoginInfo();
+ if (rememberedInfo.rememberMe && rememberedInfo.email) {
+ setFormData({
+ email: rememberedInfo.email,
+ password: rememberedInfo.password
+ });
+ setRememberMe(true);
+ }
+ }, []);
+
+ const handleEmailChange = (e) => {
+ const value = e.target.value;
+ setFormData(prev => ({
+ ...prev,
+ email: value
+ }));
+
+ // 清除邮箱错误提示
+ if (errors.email) {
+ setErrors(prev => ({
+ ...prev,
+ email: ''
+ }));
+ }
+ };
+
+ const handlePasswordChange = (e) => {
+ const value = e.target.value;
+ setFormData(prev => ({
+ ...prev,
+ password: value
+ }));
+
+ // 清除密码错误提示
+ if (errors.password) {
+ setErrors(prev => ({
+ ...prev,
+ password: ''
+ }));
+ }
+ };
+
+ const handleRememberMeChange = (e) => {
+ const checked = e.target.checked;
+ setRememberMe(checked);
+
+ // 如果取消记住我,清除已保存的登录信息
+ if (!checked) {
+ saveRememberedLoginInfo('', '', false);
+ }
+ };
+
+ const validateForm = () => {
+ const newErrors = {
+ email: '',
+ password: ''
+ };
+
+ let hasError = false;
+
+ // 验证邮箱
+ if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+ newErrors.email = '请输入邮箱地址';
+ hasError = true;
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+ newErrors.email = '请输入有效的邮箱地址';
+ hasError = true;
+ }
+
+ // 验证密码
+ if (!formData.password || typeof formData.password !== 'string' || !formData.password.trim()) {
+ newErrors.password = '请输入密码';
+ hasError = true;
+ } else if (formData.password.length < 6) {
+ newErrors.password = '密码长度至少6位';
+ hasError = true;
+ }
+
+ setErrors(newErrors);
+ return !hasError;
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ // 验证表单
+ if (!validateForm()) {
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ // 发送登录请求到后端
+ const response = await fetch(baseURL + '/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ email: formData.email, // 后端支持邮箱登录
+ password: hashPassword(formData.password) // 前端加密密码
+ })
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ // 显示成功提示
+ showSuccessAlert('登录成功!正在跳转...');
+
+ // 保存认证信息
+ saveAuthInfo(result.token, result.user, rememberMe);
+
+ // 保存或清除记住的登录信息
+ saveRememberedLoginInfo(formData.email, formData.password, rememberMe);
+
+ // 延迟跳转,让用户看到成功提示
+ setTimeout(() => {
+ window.location.href = '/test-dashboard';
+ }, 1500);
+ } else {
+ // 登录失败,显示错误信息
+ let errorTitle = '登录失败';
+ let errorContent = result.message || '登录失败,请检查您的邮箱和密码';
+
+ // 根据错误类型提供更详细的信息
+ if (result.message) {
+ if (result.message.includes('邮箱') || result.message.includes('email')) {
+ errorTitle = '邮箱验证失败';
+ errorContent = '您输入的邮箱地址不存在或格式不正确,请检查后重试。';
+ } else if (result.message.includes('密码') || result.message.includes('password')) {
+ errorTitle = '密码验证失败';
+ errorContent = '您输入的密码不正确,请检查后重试。如果忘记密码,请点击"忘记密码"进行重置。';
+ } else if (result.message.includes('用户不存在')) {
+ errorTitle = '用户不存在';
+ errorContent = '该邮箱尚未注册,请先注册账户或检查邮箱地址是否正确。';
+ } else if (result.message.includes('账户被锁定') || result.message.includes('locked')) {
+ errorTitle = '账户被锁定';
+ errorContent = '您的账户因安全原因被暂时锁定,请联系客服或稍后重试。';
+ }
+ }
+
+ showErrorModal(errorTitle, errorContent);
+ }
+ } catch (error) {
+ console.error('登录请求失败:', error);
+
+ // 根据错误类型显示不同的错误信息
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
+ showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
+ } else if (error.name === 'AbortError') {
+ showErrorModal('请求超时', '请求超时,请检查网络连接后重试。');
+ } else {
+ showErrorModal('登录失败', '网络连接失败,请检查网络或稍后重试。如果问题持续存在,请联系客服。');
+ }
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <div className="login-container">
+ <div className="login-background"></div>
+
+ {isLoading && (
+ <div className="loading-overlay">
+ <div className="loading-content">
+ <div className="loading-spinner-large"></div>
+ <p className="loading-text">正在登录...</p>
+ </div>
+ </div>
+ )}
+
+ <div className="login-content">
+ <div className="login-card">
+ {/* 成功提示 */}
+ {successAlert.visible && (
+ <div style={{ marginBottom: '16px' }}>
+ <Alert
+ message={successAlert.message}
+ type="success"
+ icon={<CheckCircleOutlined />}
+ showIcon
+ closable
+ onClose={() => setSuccessAlert({ visible: false, message: '' })}
+ style={{
+ borderRadius: '8px',
+ border: '1px solid #b7eb8f',
+ backgroundColor: '#f6ffed'
+ }}
+ />
+ </div>
+ )}
+
+ <div className="login-header">
+ <h1 className="login-title">欢迎来到小红书</h1>
+ <p className="login-subtitle">标记我的生活</p>
+ </div>
+
+ <form className="login-form" onSubmit={handleSubmit}>
+ <div className="form-group">
+ <Input
+ type="email"
+ id="email"
+ name="email"
+ className={`form-input ${errors.email ? 'input-error' : ''}`}
+ placeholder="请输入您的邮箱"
+ value={formData.email}
+ onChange={handleEmailChange}
+ prefix={<MailOutlined />}
+ size="large"
+ title=""
+ status={errors.email ? 'error' : ''}
+ />
+ {errors.email && (
+ <div className="error-message">
+ {errors.email}
+ </div>
+ )}
+ </div>
+
+ <div className="form-group">
+ <Input.Password
+ id="password"
+ name="password"
+ className={`form-input ${errors.password ? 'input-error' : ''}`}
+ placeholder="请输入您的密码"
+ value={formData.password}
+ onChange={handlePasswordChange}
+ prefix={<LockOutlined />}
+ size="large"
+ title=""
+ status={errors.password ? 'error' : ''}
+ />
+ {errors.password && (
+ <div className="error-message">
+ {errors.password}
+ </div>
+ )}
+ </div>
+
+ <div className="form-options">
+ <Checkbox
+ checked={rememberMe}
+ onChange={handleRememberMeChange}
+ >
+ 记住我
+ </Checkbox>
+ <Link to="/forgot-password" className="forgot-password">忘记密码?</Link>
+ </div>
+
+ <button
+ type="submit"
+ className={`login-button ${isLoading ? 'loading' : ''}`}
+ disabled={isLoading}
+ >
+ {isLoading ? (
+ <>
+ <div className="loading-spinner"></div>
+ 登录中...
+ </>
+ ) : (
+ '登录'
+ )}
+ </button>
+ </form>
+
+ <div className="signup-link">
+ <p>还没有账户? <Link to="/register">立即注册</Link></p>
+ </div>
+ </div>
+ </div>
+
+ {/* 错误弹窗 */}
+ <Modal
+ title={
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+ <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '18px' }} />
+ {errorModal.title}
+ </div>
+ }
+ open={errorModal.visible}
+ onOk={closeErrorModal}
+ onCancel={closeErrorModal}
+ okText="我知道了"
+ cancelButtonProps={{ style: { display: 'none' } }}
+ centered
+ className="error-modal"
+ >
+ <div style={{ padding: '16px 0', fontSize: '14px', lineHeight: '1.6' }}>
+ {errorModal.content}
+ </div>
+ </Modal>
+ </div>
+ );
+};
+
+export default LoginPage;