blob: bdd75d066d53828212e129dbc777ae5d5047130b [file] [log] [blame]
wu2f28f672025-06-19 14:29:30 +08001// src/pages/LoginPage/LoginPage.jsx
wu70fc8c52025-06-19 15:55:03 +08002
wu2f28f672025-06-19 14:29:30 +08003import React, { useState, useEffect } from 'react'
4import { useNavigate, Link } from 'react-router-dom'
5import { Input, Checkbox, Modal, Alert } from 'antd'
6import {
7 MailOutlined,
8 LockOutlined,
9 ExclamationCircleOutlined,
10 CheckCircleOutlined
11} from '@ant-design/icons'
12import {
13 getRememberedLoginInfo,
14 saveRememberedLoginInfo,
15 saveAuthInfo,
wu70fc8c52025-06-19 15:55:03 +080016 isLoggedIn,
17 clearAuthInfo // ← 新增
wu2f28f672025-06-19 14:29:30 +080018} from '../../utils/auth'
19import { hashPassword } from '../../utils/crypto'
20import './LoginPage.css'
TRM-codingc4b4f3d2025-06-18 19:02:46 +080021
wu2f28f672025-06-19 14:29:30 +080022const baseURL = 'http://10.126.59.25:8082'
TRM-codingc4b4f3d2025-06-18 19:02:46 +080023
wu2f28f672025-06-19 14:29:30 +080024export default function LoginPage() {
25 const navigate = useNavigate()
26
wu70fc8c52025-06-19 15:55:03 +080027 // —— 登录页加载时先清除旧的认证信息 ——
28 useEffect(() => {
29 clearAuthInfo(/* clearRemembered= */ false)
30 }, [])
31
TRM-codingc4b4f3d2025-06-18 19:02:46 +080032 const [formData, setFormData] = useState({
33 email: '',
34 password: ''
wu2f28f672025-06-19 14:29:30 +080035 })
36 const [rememberMe, setRememberMe] = useState(false)
37 const [isLoading, setIsLoading] = useState(false)
38 const [errors, setErrors] = useState({ email: '', password: '' })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080039 const [errorModal, setErrorModal] = useState({
40 visible: false,
41 title: '',
42 content: ''
wu2f28f672025-06-19 14:29:30 +080043 })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080044 const [successAlert, setSuccessAlert] = useState({
45 visible: false,
46 message: ''
wu2f28f672025-06-19 14:29:30 +080047 })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080048
49 // 显示错误弹窗
50 const showErrorModal = (title, content) => {
wu2f28f672025-06-19 14:29:30 +080051 setErrorModal({ visible: true, title, content })
52 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080053 // 关闭错误弹窗
54 const closeErrorModal = () => {
wu2f28f672025-06-19 14:29:30 +080055 setErrorModal({ visible: false, title: '', content: '' })
56 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080057 // 显示成功提示
58 const showSuccessAlert = (message) => {
wu2f28f672025-06-19 14:29:30 +080059 setSuccessAlert({ visible: true, message })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080060 setTimeout(() => {
wu2f28f672025-06-19 14:29:30 +080061 setSuccessAlert({ visible: false, message: '' })
62 }, 3000)
63 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080064
wu2f28f672025-06-19 14:29:30 +080065 // 初始化:检查登录 & 填充“记住我”
TRM-codingc4b4f3d2025-06-18 19:02:46 +080066 useEffect(() => {
TRM-codingc4b4f3d2025-06-18 19:02:46 +080067 if (isLoggedIn()) {
wu2f28f672025-06-19 14:29:30 +080068 console.log('用户已登录')
wu70fc8c52025-06-19 15:55:03 +080069 // 如果想自动跳转: navigate('/home', { replace: true })
TRM-codingc4b4f3d2025-06-18 19:02:46 +080070 }
wu2f28f672025-06-19 14:29:30 +080071 const { email, password, rememberMe } = getRememberedLoginInfo()
72 if (rememberMe && email) {
73 setFormData({ email, password })
74 setRememberMe(true)
TRM-codingc4b4f3d2025-06-18 19:02:46 +080075 }
wu70fc8c52025-06-19 15:55:03 +080076 }, [])
TRM-codingc4b4f3d2025-06-18 19:02:46 +080077
78 const handleEmailChange = (e) => {
wu2f28f672025-06-19 14:29:30 +080079 setFormData(f => ({ ...f, email: e.target.value }))
80 if (errors.email) setErrors(e => ({ ...e, email: '' }))
81 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080082 const handlePasswordChange = (e) => {
wu2f28f672025-06-19 14:29:30 +080083 setFormData(f => ({ ...f, password: e.target.value }))
84 if (errors.password) setErrors(e => ({ ...e, password: '' }))
85 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080086 const handleRememberMeChange = (e) => {
wu2f28f672025-06-19 14:29:30 +080087 const checked = e.target.checked
88 setRememberMe(checked)
TRM-codingc4b4f3d2025-06-18 19:02:46 +080089 if (!checked) {
wu2f28f672025-06-19 14:29:30 +080090 saveRememberedLoginInfo('', '', false)
TRM-codingc4b4f3d2025-06-18 19:02:46 +080091 }
wu2f28f672025-06-19 14:29:30 +080092 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +080093
94 const validateForm = () => {
wu2f28f672025-06-19 14:29:30 +080095 const newErr = { email: '', password: '' }
96 let hasError = false
97 if (!formData.email.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
98 newErr.email = '请输入有效的邮箱地址'
99 hasError = true
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800100 }
wu2f28f672025-06-19 14:29:30 +0800101 if (!formData.password.trim() || formData.password.length < 6) {
102 newErr.password = '密码长度至少6位'
103 hasError = true
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800104 }
wu2f28f672025-06-19 14:29:30 +0800105 setErrors(newErr)
106 return !hasError
107 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800108
109 const handleSubmit = async (e) => {
wu2f28f672025-06-19 14:29:30 +0800110 e.preventDefault()
111 if (!validateForm()) return
112
113 setIsLoading(true)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800114 try {
wu2f28f672025-06-19 14:29:30 +0800115 const res = await fetch(baseURL + '/login', {
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800116 method: 'POST',
wu2f28f672025-06-19 14:29:30 +0800117 headers: { 'Content-Type': 'application/json' },
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800118 body: JSON.stringify({
wu2f28f672025-06-19 14:29:30 +0800119 email: formData.email,
120 password: hashPassword(formData.password)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800121 })
wu2f28f672025-06-19 14:29:30 +0800122 })
123 const result = await res.json()
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800124 if (result.success) {
wu2f28f672025-06-19 14:29:30 +0800125 showSuccessAlert('登录成功!正在跳转...')
126 saveAuthInfo(result.token, result.user, rememberMe)
127 saveRememberedLoginInfo(formData.email, formData.password, rememberMe)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800128 setTimeout(() => {
wu70fc8c52025-06-19 15:55:03 +0800129 const uid = result.user.id
wu2f28f672025-06-19 14:29:30 +0800130 switch (result.user.role) {
131 case 'admin':
wu70fc8c52025-06-19 15:55:03 +0800132 navigate(`/admin/${uid}`, { replace: true })
wu2f28f672025-06-19 14:29:30 +0800133 break
134 case 'superadmin':
wu70fc8c52025-06-19 15:55:03 +0800135 navigate(`/superadmin/${uid}/users`, { replace: true })
wu2f28f672025-06-19 14:29:30 +0800136 break
137 default:
138 navigate('/home', { replace: true })
139 }
140 }, 1500)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800141 } else {
wu2f28f672025-06-19 14:29:30 +0800142 let title = '登录失败'
143 let content = result.message || '登录失败,请检查您的邮箱和密码'
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800144 if (result.message) {
wu2f28f672025-06-19 14:29:30 +0800145 if (/邮箱|email/.test(result.message)) {
146 title = '邮箱验证失败'
147 content = '请输入正确的邮箱地址'
148 } else if (/密码|password/.test(result.message)) {
149 title = '密码验证失败'
150 content = '密码不正确,请重试'
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800151 }
152 }
wu2f28f672025-06-19 14:29:30 +0800153 showErrorModal(title, content)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800154 }
wu2f28f672025-06-19 14:29:30 +0800155 } catch (err) {
156 console.error(err)
157 showErrorModal('网络异常', '无法连接到服务器,请稍后重试')
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800158 } finally {
wu2f28f672025-06-19 14:29:30 +0800159 setIsLoading(false)
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800160 }
wu2f28f672025-06-19 14:29:30 +0800161 }
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800162
163 return (
164 <div className="login-container">
wu2f28f672025-06-19 14:29:30 +0800165 <div className="login-background" />
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800166 {isLoading && (
167 <div className="loading-overlay">
168 <div className="loading-content">
wu2f28f672025-06-19 14:29:30 +0800169 <div className="loading-spinner-large" />
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800170 <p className="loading-text">正在登录...</p>
171 </div>
172 </div>
173 )}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800174 <div className="login-content">
175 <div className="login-card">
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800176 {successAlert.visible && (
wu2f28f672025-06-19 14:29:30 +0800177 <Alert
178 message={successAlert.message}
179 type="success"
180 icon={<CheckCircleOutlined />}
181 closable
182 style={{ marginBottom: 16, borderRadius: 8 }}
183 />
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800184 )}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800185 <div className="login-header">
wu2f28f672025-06-19 14:29:30 +0800186 <h1>欢迎来到小红书</h1>
187 <p>标记我的生活</p>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800188 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800189 <form className="login-form" onSubmit={handleSubmit}>
190 <div className="form-group">
191 <Input
192 type="email"
wu2f28f672025-06-19 14:29:30 +0800193 placeholder="邮箱"
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800194 value={formData.email}
195 onChange={handleEmailChange}
196 prefix={<MailOutlined />}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800197 status={errors.email ? 'error' : ''}
198 />
wu2f28f672025-06-19 14:29:30 +0800199 {errors.email && <div className="error-message">{errors.email}</div>}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800200 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800201 <div className="form-group">
202 <Input.Password
wu2f28f672025-06-19 14:29:30 +0800203 placeholder="密码"
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800204 value={formData.password}
205 onChange={handlePasswordChange}
206 prefix={<LockOutlined />}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800207 status={errors.password ? 'error' : ''}
208 />
wu2f28f672025-06-19 14:29:30 +0800209 {errors.password && <div className="error-message">{errors.password}</div>}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800210 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800211 <div className="form-options">
wu2f28f672025-06-19 14:29:30 +0800212 <Checkbox checked={rememberMe} onChange={handleRememberMeChange}>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800213 记住我
214 </Checkbox>
wu2f28f672025-06-19 14:29:30 +0800215 <Link to="/forgot-password">忘记密码?</Link>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800216 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800217 <button
218 type="submit"
219 className={`login-button ${isLoading ? 'loading' : ''}`}
220 disabled={isLoading}
221 >
wu2f28f672025-06-19 14:29:30 +0800222 {isLoading ? '登录中...' : '登录'}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800223 </button>
224 </form>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800225 <div className="signup-link">
wu2f28f672025-06-19 14:29:30 +0800226 <p>还没有账户?<Link to="/register">立即注册</Link></p>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800227 </div>
228 </div>
229 </div>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800230 <Modal
wu2f28f672025-06-19 14:29:30 +0800231 title={<><ExclamationCircleOutlined style={{ color: '#ff4d4f' }} /> {errorModal.title}</>}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800232 open={errorModal.visible}
233 onOk={closeErrorModal}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800234 cancelButtonProps={{ style: { display: 'none' } }}
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800235 >
wu2f28f672025-06-19 14:29:30 +0800236 <p>{errorModal.content}</p>
TRM-codingc4b4f3d2025-06-18 19:02:46 +0800237 </Modal>
238 </div>
wu2f28f672025-06-19 14:29:30 +0800239 )
240}