blob: 31453a4fad4ad64e8726450e611d3029b3055032 [file] [log] [blame]
wu2f28f672025-06-19 14:29:30 +08001// src/pages/LoginPage/LoginPage.jsx
2import React, { useState, useEffect } from 'react'
3import { useNavigate, Link } from 'react-router-dom'
4import { Input, Checkbox, Modal, Alert } from 'antd'
5import {
6 MailOutlined,
7 LockOutlined,
8 ExclamationCircleOutlined,
9 CheckCircleOutlined
10} from '@ant-design/icons'
11import {
12 getRememberedLoginInfo,
13 saveRememberedLoginInfo,
14 saveAuthInfo,
15 isLoggedIn
16} from '../../utils/auth'
17import { hashPassword } from '../../utils/crypto'
18import './LoginPage.css'
TRM-codingc4b4f3d2025-06-18 19:02:46 +080019
wu2f28f672025-06-19 14:29:30 +080020const baseURL = 'http://10.126.59.25:8082'
TRM-codingc4b4f3d2025-06-18 19:02:46 +080021
wu2f28f672025-06-19 14:29:30 +080022export default function LoginPage() {
23 const navigate = useNavigate()
24
TRM-codingc4b4f3d2025-06-18 19:02:46 +080025 const [formData, setFormData] = useState({
26 email: '',
27 password: ''
wu2f28f672025-06-19 14:29:30 +080028 })
29 const [rememberMe, setRememberMe] = useState(false)
30 const [isLoading, setIsLoading] = useState(false)
31 const [errors, setErrors] = useState({ email: '', password: '' })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080032 const [errorModal, setErrorModal] = useState({
33 visible: false,
34 title: '',
35 content: ''
wu2f28f672025-06-19 14:29:30 +080036 })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080037 const [successAlert, setSuccessAlert] = useState({
38 visible: false,
39 message: ''
wu2f28f672025-06-19 14:29:30 +080040 })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080041
42 // 显示错误弹窗
43 const showErrorModal = (title, content) => {
wu2f28f672025-06-19 14:29:30 +080044 setErrorModal({ visible: true, title, content })
45 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080046 // 关闭错误弹窗
47 const closeErrorModal = () => {
wu2f28f672025-06-19 14:29:30 +080048 setErrorModal({ visible: false, title: '', content: '' })
49 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080050 // 显示成功提示
51 const showSuccessAlert = (message) => {
wu2f28f672025-06-19 14:29:30 +080052 setSuccessAlert({ visible: true, message })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080053 setTimeout(() => {
wu2f28f672025-06-19 14:29:30 +080054 setSuccessAlert({ visible: false, message: '' })
55 }, 3000)
56 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080057
wu2f28f672025-06-19 14:29:30 +080058 // 初始化:检查登录 & 填充“记住我”
TRM-codingc4b4f3d2025-06-18 19:02:46 +080059 useEffect(() => {
TRM-codingc4b4f3d2025-06-18 19:02:46 +080060 if (isLoggedIn()) {
wu2f28f672025-06-19 14:29:30 +080061 console.log('用户已登录')
62 // 如果要自动跳转可以在这里:
63 // navigate('/home', { replace: true })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080064 }
wu2f28f672025-06-19 14:29:30 +080065 const { email, password, rememberMe } = getRememberedLoginInfo()
66 if (rememberMe && email) {
67 setFormData({ email, password })
68 setRememberMe(true)
TRM-codingc4b4f3d2025-06-18 19:02:46 +080069 }
wu2f28f672025-06-19 14:29:30 +080070 }, [navigate])
TRM-codingc4b4f3d2025-06-18 19:02:46 +080071
72 const handleEmailChange = (e) => {
wu2f28f672025-06-19 14:29:30 +080073 setFormData(f => ({ ...f, email: e.target.value }))
74 if (errors.email) setErrors(e => ({ ...e, email: '' }))
75 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080076 const handlePasswordChange = (e) => {
wu2f28f672025-06-19 14:29:30 +080077 setFormData(f => ({ ...f, password: e.target.value }))
78 if (errors.password) setErrors(e => ({ ...e, password: '' }))
79 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080080 const handleRememberMeChange = (e) => {
wu2f28f672025-06-19 14:29:30 +080081 const checked = e.target.checked
82 setRememberMe(checked)
TRM-codingc4b4f3d2025-06-18 19:02:46 +080083 if (!checked) {
wu2f28f672025-06-19 14:29:30 +080084 saveRememberedLoginInfo('', '', false)
TRM-codingc4b4f3d2025-06-18 19:02:46 +080085 }
wu2f28f672025-06-19 14:29:30 +080086 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080087
88 const validateForm = () => {
wu2f28f672025-06-19 14:29:30 +080089 const newErr = { email: '', password: '' }
90 let hasError = false
91 if (!formData.email.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
92 newErr.email = '请输入有效的邮箱地址'
93 hasError = true
TRM-codingc4b4f3d2025-06-18 19:02:46 +080094 }
wu2f28f672025-06-19 14:29:30 +080095 if (!formData.password.trim() || formData.password.length < 6) {
96 newErr.password = '密码长度至少6位'
97 hasError = true
TRM-codingc4b4f3d2025-06-18 19:02:46 +080098 }
wu2f28f672025-06-19 14:29:30 +080099 setErrors(newErr)
100 return !hasError
101 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800102
103 const handleSubmit = async (e) => {
wu2f28f672025-06-19 14:29:30 +0800104 e.preventDefault()
105 if (!validateForm()) return
106
107 setIsLoading(true)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800108 try {
wu2f28f672025-06-19 14:29:30 +0800109 const res = await fetch(baseURL + '/login', {
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800110 method: 'POST',
wu2f28f672025-06-19 14:29:30 +0800111 headers: { 'Content-Type': 'application/json' },
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800112 body: JSON.stringify({
wu2f28f672025-06-19 14:29:30 +0800113 email: formData.email,
114 password: hashPassword(formData.password)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800115 })
wu2f28f672025-06-19 14:29:30 +0800116 })
117 const result = await res.json()
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800118 if (result.success) {
wu2f28f672025-06-19 14:29:30 +0800119 showSuccessAlert('登录成功!正在跳转...')
120 saveAuthInfo(result.token, result.user, rememberMe)
121 saveRememberedLoginInfo(formData.email, formData.password, rememberMe)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800122 setTimeout(() => {
wu2f28f672025-06-19 14:29:30 +0800123 // 根据不同角色跳转
124 switch (result.user.role) {
125 case 'admin':
126 navigate('/admin', { replace: true })
127 break
128 case 'superadmin':
129 navigate('/superadmin', { replace: true })
130 break
131 default:
132 navigate('/home', { replace: true })
133 }
134 }, 1500)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800135 } else {
wu2f28f672025-06-19 14:29:30 +0800136 let title = '登录失败'
137 let content = result.message || '登录失败,请检查您的邮箱和密码'
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800138 if (result.message) {
wu2f28f672025-06-19 14:29:30 +0800139 if (/邮箱|email/.test(result.message)) {
140 title = '邮箱验证失败'
141 content = '请输入正确的邮箱地址'
142 } else if (/密码|password/.test(result.message)) {
143 title = '密码验证失败'
144 content = '密码不正确,请重试'
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800145 }
146 }
wu2f28f672025-06-19 14:29:30 +0800147 showErrorModal(title, content)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800148 }
wu2f28f672025-06-19 14:29:30 +0800149 } catch (err) {
150 console.error(err)
151 showErrorModal('网络异常', '无法连接到服务器,请稍后重试')
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800152 } finally {
wu2f28f672025-06-19 14:29:30 +0800153 setIsLoading(false)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800154 }
wu2f28f672025-06-19 14:29:30 +0800155 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800156
157 return (
158 <div className="login-container">
wu2f28f672025-06-19 14:29:30 +0800159 <div className="login-background" />
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800160 {isLoading && (
161 <div className="loading-overlay">
162 <div className="loading-content">
wu2f28f672025-06-19 14:29:30 +0800163 <div className="loading-spinner-large" />
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800164 <p className="loading-text">正在登录...</p>
165 </div>
166 </div>
167 )}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800168 <div className="login-content">
169 <div className="login-card">
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800170 {successAlert.visible && (
wu2f28f672025-06-19 14:29:30 +0800171 <Alert
172 message={successAlert.message}
173 type="success"
174 icon={<CheckCircleOutlined />}
175 closable
176 style={{ marginBottom: 16, borderRadius: 8 }}
177 />
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800178 )}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800179 <div className="login-header">
wu2f28f672025-06-19 14:29:30 +0800180 <h1>欢迎来到小红书</h1>
181 <p>标记我的生活</p>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800182 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800183 <form className="login-form" onSubmit={handleSubmit}>
184 <div className="form-group">
185 <Input
186 type="email"
wu2f28f672025-06-19 14:29:30 +0800187 placeholder="邮箱"
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800188 value={formData.email}
189 onChange={handleEmailChange}
190 prefix={<MailOutlined />}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800191 status={errors.email ? 'error' : ''}
192 />
wu2f28f672025-06-19 14:29:30 +0800193 {errors.email && <div className="error-message">{errors.email}</div>}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800194 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800195 <div className="form-group">
196 <Input.Password
wu2f28f672025-06-19 14:29:30 +0800197 placeholder="密码"
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800198 value={formData.password}
199 onChange={handlePasswordChange}
200 prefix={<LockOutlined />}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800201 status={errors.password ? 'error' : ''}
202 />
wu2f28f672025-06-19 14:29:30 +0800203 {errors.password && <div className="error-message">{errors.password}</div>}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800204 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800205 <div className="form-options">
wu2f28f672025-06-19 14:29:30 +0800206 <Checkbox checked={rememberMe} onChange={handleRememberMeChange}>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800207 记住我
208 </Checkbox>
wu2f28f672025-06-19 14:29:30 +0800209 <Link to="/forgot-password">忘记密码?</Link>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800210 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800211 <button
212 type="submit"
213 className={`login-button ${isLoading ? 'loading' : ''}`}
214 disabled={isLoading}
215 >
wu2f28f672025-06-19 14:29:30 +0800216 {isLoading ? '登录中...' : '登录'}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800217 </button>
218 </form>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800219 <div className="signup-link">
wu2f28f672025-06-19 14:29:30 +0800220 <p>还没有账户?<Link to="/register">立即注册</Link></p>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800221 </div>
222 </div>
223 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800224 <Modal
wu2f28f672025-06-19 14:29:30 +0800225 title={<><ExclamationCircleOutlined style={{ color: '#ff4d4f' }} /> {errorModal.title}</>}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800226 open={errorModal.visible}
227 onOk={closeErrorModal}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800228 cancelButtonProps={{ style: { display: 'none' } }}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800229 >
wu2f28f672025-06-19 14:29:30 +0800230 <p>{errorModal.content}</p>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800231 </Modal>
232 </div>
wu2f28f672025-06-19 14:29:30 +0800233 )
234}