blob: 836e1cfef367fa0118f7fb5293df23cd2ee2efa3 [file] [log] [blame]
TRM-codingc4b4f3d2025-06-18 19:02:46 +08001import React, { useState } from 'react';
2import { Link, useNavigate } from 'react-router-dom';
3import { Input, Button, message, Modal, Alert } from 'antd';
4import { MailOutlined, LockOutlined, UserOutlined, SafetyOutlined, ExclamationCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
5import { hashPassword } from '../../utils/crypto';
6import './RegisterPage.css';
7
8const baseURL = 'http://10.126.59.25:8082';
9
10const RegisterPage = () => {
11 const [formData, setFormData] = useState({
12 username: '',
13 email: '',
14 emailCode: '',
15 password: '',
16 confirmPassword: ''
17 });
18
19 const [errors, setErrors] = useState({
20 username: '',
21 email: '',
22 emailCode: '',
23 password: '',
24 confirmPassword: ''
25 });
26
27 const [emailCodeSent, setEmailCodeSent] = useState(false);
28 const [countdown, setCountdown] = useState(0);
29 const [sendingCode, setSendingCode] = useState(false);
30 const [isLoading, setIsLoading] = useState(false);
31 const [errorModal, setErrorModal] = useState({
32 visible: false,
33 title: '',
34 content: ''
35 });
36 const [successAlert, setSuccessAlert] = useState({
37 visible: false,
38 message: ''
39 });
40 const [emailCodeSuccessAlert, setEmailCodeSuccessAlert] = useState({
41 visible: false,
42 message: ''
43 });
44
45 const navigate = useNavigate();
46
47 // 显示错误弹窗
48 const showErrorModal = (title, content) => {
49 setErrorModal({
50 visible: true,
51 title: title,
52 content: content
53 });
54 };
55
56 // 关闭错误弹窗
57 const closeErrorModal = () => {
58 setErrorModal({
59 visible: false,
60 title: '',
61 content: ''
62 });
63 };
64
65 // 显示成功提示
66 const showSuccessAlert = (message) => {
67 setSuccessAlert({
68 visible: true,
69 message: message
70 });
71
72 // 3秒后自动隐藏
73 setTimeout(() => {
74 setSuccessAlert({
75 visible: false,
76 message: ''
77 });
78 }, 3000);
79 };
80
81 // 显示邮件验证码发送成功提示
82 const showEmailCodeSuccessAlert = (message) => {
83 setEmailCodeSuccessAlert({
84 visible: true,
85 message: message
86 });
87
88 // 5秒后自动隐藏
89 setTimeout(() => {
90 setEmailCodeSuccessAlert({
91 visible: false,
92 message: ''
93 });
94 }, 5000);
95 };
96
97 // 倒计时效果
98 React.useEffect(() => {
99 let timer;
100 if (countdown > 0) {
101 timer = setTimeout(() => {
102 setCountdown(countdown - 1);
103 }, 1000);
104 }
105 return () => clearTimeout(timer);
106 }, [countdown]);
107
108 // 发送邮箱验证码
109 const sendEmailCode = async () => {
110 // 验证邮箱格式
111 if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
112 setErrors(prev => ({
113 ...prev,
114 email: '请先输入邮箱地址'
115 }));
116 return;
117 }
118
119 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
120 setErrors(prev => ({
121 ...prev,
122 email: '请输入有效的邮箱地址'
123 }));
124 return;
125 }
126
127 setSendingCode(true);
128
129 try {
130 // 调用后端API发送验证码
131 const response = await fetch(baseURL + '/send-verification-code', {
132 method: 'POST',
133 headers: {
134 'Content-Type': 'application/json',
135 },
136 body: JSON.stringify({
137 email: formData.email,
138 verification_type: 'register'
139 })
140 });
141
142 if (!response.ok) {
143 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
144 }
145
146 const result = await response.json();
147
148 if (result.success) {
149 showEmailCodeSuccessAlert('验证码已发送到您的邮箱');
150 setEmailCodeSent(true);
151 setCountdown(60); // 60秒倒计时
152
153 // 清除邮箱错误提示
154 setErrors(prev => ({
155 ...prev,
156 email: ''
157 }));
158 } else {
159 // 根据具体错误信息进行处理
160 const errorMessage = result.message || '发送验证码失败,请稍后再试';
161
162 if (errorMessage.includes('邮箱') && (errorMessage.includes('已注册') || errorMessage.includes('已存在'))) {
163 setErrors(prev => ({
164 ...prev,
165 email: errorMessage
166 }));
167 } else {
168 showErrorModal('发送验证码失败', errorMessage);
169 }
170 }
171
172 } catch (error) {
173 console.error('发送验证码失败:', error);
174
175 // 根据错误类型显示不同的错误信息
176 if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
177 showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
178 } else if (error.message.includes('HTTP 500')) {
179 showErrorModal('服务器错误', '服务器出现了内部错误,请稍后重试。如果问题持续存在,请联系客服。');
180 } else if (error.message.includes('HTTP 429')) {
181 showErrorModal('发送频率限制', '验证码发送过于频繁,请稍后再试。为了防止垃圾邮件,系统限制了发送频率。');
182 } else if (error.message.includes('HTTP 400')) {
183 showErrorModal('请求错误', '邮箱格式错误,请检查邮箱地址是否正确。');
184 } else {
185 showErrorModal('发送失败', '发送验证码失败,请稍后重试。如果问题持续存在,请联系客服。');
186 }
187 } finally {
188 setSendingCode(false);
189 }
190 };
191
192 const handleInputChange = (field) => (e) => {
193 const value = e.target.value;
194 setFormData(prev => ({
195 ...prev,
196 [field]: value
197 }));
198
199 // 清除对应字段的错误提示
200 if (errors[field]) {
201 setErrors(prev => ({
202 ...prev,
203 [field]: ''
204 }));
205 }
206 };
207
208 const handlePasswordChange = (e) => {
209 const value = e.target.value;
210 setFormData(prev => ({
211 ...prev,
212 password: value
213 }));
214
215 // 清除密码错误提示
216 if (errors.password) {
217 setErrors(prev => ({
218 ...prev,
219 password: ''
220 }));
221 }
222 };
223
224 const handleConfirmPasswordChange = (e) => {
225 const value = e.target.value;
226 setFormData(prev => ({
227 ...prev,
228 confirmPassword: value
229 }));
230
231 // 清除确认密码错误提示
232 if (errors.confirmPassword) {
233 setErrors(prev => ({
234 ...prev,
235 confirmPassword: ''
236 }));
237 }
238 };
239
240 const validateForm = () => {
241 const newErrors = {
242 username: '',
243 email: '',
244 emailCode: '',
245 password: '',
246 confirmPassword: ''
247 };
248
249 let hasError = false;
250
251 // 验证用户名
252 if (!formData.username || typeof formData.username !== 'string' || !formData.username.trim()) {
253 newErrors.username = '请输入用户名';
254 hasError = true;
255 } else if (formData.username.length < 2) {
256 newErrors.username = '用户名至少2个字符';
257 hasError = true;
258 } else if (formData.username.length > 20) {
259 newErrors.username = '用户名不能超过20个字符';
260 hasError = true;
261 }
262
263 // 验证邮箱
264 if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
265 newErrors.email = '请输入邮箱地址';
266 hasError = true;
267 } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
268 newErrors.email = '请输入有效的邮箱地址';
269 hasError = true;
270 }
271
272 // 验证邮箱验证码
273 if (!formData.emailCode || typeof formData.emailCode !== 'string' || !formData.emailCode.trim()) {
274 newErrors.emailCode = '请输入邮箱验证码';
275 hasError = true;
276 } else if (formData.emailCode.length !== 6 || !/^\d{6}$/.test(formData.emailCode)) {
277 newErrors.emailCode = '请输入6位数字验证码';
278 hasError = true;
279 }
280
281 // 验证密码
282 if (!formData.password || typeof formData.password !== 'string' || !formData.password.trim()) {
283 newErrors.password = '请输入密码';
284 hasError = true;
285 } else if (formData.password.length < 6) {
286 newErrors.password = '密码长度至少6位';
287 hasError = true;
288 } else if (formData.password.length > 20) {
289 newErrors.password = '密码长度不能超过20位';
290 hasError = true;
291 }
292
293 // 验证确认密码
294 if (!formData.confirmPassword || typeof formData.confirmPassword !== 'string' || !formData.confirmPassword.trim()) {
295 newErrors.confirmPassword = '请确认密码';
296 hasError = true;
297 } else if (formData.password !== formData.confirmPassword) {
298 newErrors.confirmPassword = '两次输入的密码不一致';
299 hasError = true;
300 }
301
302 setErrors(newErrors);
303 return !hasError;
304 };
305
306 const handleSubmit = async (e) => {
307 e.preventDefault();
308
309 // 验证表单
310 if (!validateForm()) {
311 return;
312 }
313
314 setIsLoading(true);
315
316 try {
317 // 调用后端API进行注册
318 const registerResponse = await fetch(baseURL + '/register', {
319 method: 'POST',
320 headers: {
321 'Content-Type': 'application/json',
322 },
323 body: JSON.stringify({
324 username: formData.username,
325 email: formData.email,
326 password: hashPassword(formData.password), // 前端加密密码
327 verification_code: hashPassword(formData.emailCode) // 前端加密验证码
328 })
329 });
330
331 if (!registerResponse.ok) {
332 throw new Error(`HTTP ${registerResponse.status}: ${registerResponse.statusText}`);
333 }
334
335 const registerResult = await registerResponse.json();
336
337 if (registerResult.success) {
338 showSuccessAlert('注册成功!欢迎加入小红书,正在跳转到登录页面...');
339 // 清空表单数据
340 setFormData({
341 username: '',
342 email: '',
343 emailCode: '',
344 password: '',
345 confirmPassword: ''
346 });
347 setErrors({
348 username: '',
349 email: '',
350 emailCode: '',
351 password: '',
352 confirmPassword: ''
353 });
354 // 延迟跳转到登录页面,让用户看到成功提示
355 setTimeout(() => {
356 navigate('/login');
357 }, 2000);
358 } else {
359 // 处理不同的注册失败情况
360 const errorMessage = registerResult.message || '注册失败,请稍后再试';
361
362 // 如果是邮箱已存在的错误,将错误显示在邮箱字段下方
363 if (errorMessage.includes('邮箱') && (errorMessage.includes('已存在') || errorMessage.includes('已注册'))) {
364 setErrors(prev => ({
365 ...prev,
366 email: errorMessage
367 }));
368 }
369 // 如果是用户名已存在的错误,将错误显示在用户名字段下方
370 else if (errorMessage.includes('用户名') && (errorMessage.includes('已存在') || errorMessage.includes('已被使用'))) {
371 setErrors(prev => ({
372 ...prev,
373 username: errorMessage
374 }));
375 }
376 else {
377 // 其他错误显示在消息框中
378 message.error(errorMessage);
379 }
380 }
381
382 } catch (error) {
383 console.error('注册失败:', error);
384
385 // 根据错误类型显示不同的错误信息
386 if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
387 showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
388 } else if (error.message.includes('HTTP 500')) {
389 showErrorModal('服务器内部错误', '服务器出现了内部错误,请稍后重试或联系客服。技术团队已收到通知。');
390 } else if (error.message.includes('HTTP 400')) {
391 showErrorModal('请求参数错误', '请求参数有误,请检查您输入的信息是否正确。');
392 } else if (error.message.includes('HTTP 409')) {
393 showErrorModal('用户信息冲突', '您输入的邮箱或用户名可能已被其他用户使用,请尝试使用其他邮箱或用户名。');
394 } else if (error.message.includes('HTTP')) {
395 showErrorModal('请求失败', `请求失败 (${error.message}),请稍后重试。如果问题持续存在,请联系客服。`);
396 } else {
397 showErrorModal('注册失败', '注册过程中发生未知错误,请稍后重试。如果问题持续存在,请联系客服。');
398 }
399 } finally {
400 setIsLoading(false);
401 }
402 };
403
404 return (
405 <div className="register-container">
406 <div className="register-background"></div>
407
408 {isLoading && (
409 <div className="loading-overlay">
410 <div className="loading-content">
411 <div className="loading-spinner-large"></div>
412 <p className="loading-text">正在注册...</p>
413 </div>
414 </div>
415 )}
416
417 <div className="register-content">
418 <div className="register-card">
419 {/* 成功提示 */}
420 {successAlert.visible && (
421 <div style={{ marginBottom: '16px' }}>
422 <Alert
423 message={successAlert.message}
424 type="success"
425 icon={<CheckCircleOutlined />}
426 showIcon
427 closable
428 onClose={() => setSuccessAlert({ visible: false, message: '' })}
429 style={{
430 borderRadius: '8px',
431 border: '1px solid #b7eb8f',
432 backgroundColor: '#f6ffed'
433 }}
434 />
435 </div>
436 )}
437
438 {/* 邮件验证码发送成功提示 */}
439 {emailCodeSuccessAlert.visible && (
440 <div style={{ marginBottom: '16px' }}>
441 <Alert
442 message={emailCodeSuccessAlert.message}
443 type="success"
444 icon={<CheckCircleOutlined />}
445 showIcon
446 closable
447 onClose={() => setEmailCodeSuccessAlert({ visible: false, message: '' })}
448 style={{
449 borderRadius: '8px',
450 border: '1px solid #b7eb8f',
451 backgroundColor: '#f6ffed'
452 }}
453 />
454 </div>
455 )}
456
457 <div className="register-header">
458 <h1 className="register-title">加入小红书</h1>
459 <p className="register-subtitle">发现美好生活,分享精彩瞬间</p>
460 </div>
461
462 <form className="register-form" onSubmit={handleSubmit}>
463 <div className="form-group">
464 <Input
465 type="text"
466 id="username"
467 name="username"
468 className={`form-input ${errors.username ? 'input-error' : ''}`}
469 placeholder="请输入用户名"
470 value={formData.username}
471 onChange={handleInputChange('username')}
472 prefix={<UserOutlined />}
473 size="large"
474 title=""
475 status={errors.username ? 'error' : ''}
476 />
477 {errors.username && (
478 <div className="error-message">
479 {errors.username}
480 </div>
481 )}
482 </div>
483
484 <div className="form-group">
485 <Input
486 type="email"
487 id="email"
488 name="email"
489 className={`form-input ${errors.email ? 'input-error' : ''}`}
490 placeholder="请输入邮箱地址"
491 value={formData.email}
492 onChange={handleInputChange('email')}
493 prefix={<MailOutlined />}
494 size="large"
495 title=""
496 status={errors.email ? 'error' : ''}
497 />
498 {errors.email && (
499 <div className="error-message">
500 {errors.email}
501 </div>
502 )}
503 </div>
504
505 <div className="form-group">
506 <div className="email-code-wrapper">
507 <Input
508 type="text"
509 id="emailCode"
510 name="emailCode"
511 className={`form-input email-code-input ${errors.emailCode ? 'input-error' : ''}`}
512 placeholder="请输入6位验证码"
513 value={formData.emailCode}
514 onChange={handleInputChange('emailCode')}
515 prefix={<SafetyOutlined />}
516 maxLength={6}
517 size="large"
518 title=""
519 status={errors.emailCode ? 'error' : ''}
520 />
521 <Button
522 type="primary"
523 className="send-code-button"
524 onClick={sendEmailCode}
525 loading={sendingCode}
526 disabled={countdown > 0 || !formData.email || sendingCode}
527 size="large"
528 >
529 {countdown > 0 ? `${countdown}s后重发` : (emailCodeSent ? '重新发送' : '发送验证码')}
530 </Button>
531 </div>
532 {errors.emailCode && (
533 <div className="error-message">
534 {errors.emailCode}
535 </div>
536 )}
537 </div>
538
539 <div className="form-group">
540 <Input.Password
541 id="password"
542 name="password"
543 className={`form-input ${errors.password ? 'input-error' : ''}`}
544 placeholder="请输入密码"
545 value={formData.password}
546 onChange={handlePasswordChange}
547 prefix={<LockOutlined />}
548 size="large"
549 title=""
550 status={errors.password ? 'error' : ''}
551 />
552 {errors.password && (
553 <div className="error-message">
554 {errors.password}
555 </div>
556 )}
557 </div>
558
559 <div className="form-group">
560 <Input.Password
561 id="confirmPassword"
562 name="confirmPassword"
563 className={`form-input ${errors.confirmPassword ? 'input-error' : ''}`}
564 placeholder="请确认密码"
565 value={formData.confirmPassword}
566 onChange={handleConfirmPasswordChange}
567 prefix={<LockOutlined />}
568 size="large"
569 title=""
570 status={errors.confirmPassword ? 'error' : ''}
571 />
572 {errors.confirmPassword && (
573 <div className="error-message">
574 {errors.confirmPassword}
575 </div>
576 )}
577 </div>
578
579 <button
580 type="submit"
581 className={`register-button ${isLoading ? 'loading' : ''}`}
582 disabled={isLoading}
583 >
584 {isLoading ? (
585 <>
586 <div className="loading-spinner"></div>
587 注册中...
588 </>
589 ) : (
590 '立即注册'
591 )}
592 </button>
593 </form>
594
595 <div className="login-link">
596 <p>已有账户? <Link to="/login">立即登录</Link></p>
597 </div>
598 </div>
599 </div>
600
601 {/* 错误弹窗 */}
602 <Modal
603 title={
604 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
605 <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '18px' }} />
606 {errorModal.title}
607 </div>
608 }
609 open={errorModal.visible}
610 onOk={closeErrorModal}
611 onCancel={closeErrorModal}
612 okText="我知道了"
613 cancelButtonProps={{ style: { display: 'none' } }}
614 centered
615 className="error-modal"
616 >
617 <div style={{ padding: '16px 0', fontSize: '14px', lineHeight: '1.6' }}>
618 {errorModal.content}
619 </div>
620 </Modal>
621 </div>
622 );
623};
624
625export default RegisterPage;