From 25d10113c4d27ccdea506e352b0bdd194560b0dc Mon Sep 17 00:00:00 2001
From: hongjli <3117313295@qq.com>
Date: 星期三, 16 四月 2025 09:15:31 +0800
Subject: [PATCH] 注册
---
src/app/register/page.tsx | 206 ++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 177 insertions(+), 29 deletions(-)
diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx
index 55b505e..ead9ed1 100644
--- a/src/app/register/page.tsx
+++ b/src/app/register/page.tsx
@@ -2,8 +2,29 @@
import { useState, useEffect } from 'react';
import Image from 'next/image';
-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
import Link from 'next/link';
+import { useRouter } from 'next/navigation';
+import ApiService from '@/utils/api';
+
+// Toast 閫氱煡缁勪欢
+const Toast = ({ message, type, onClose }: { message: string; type: 'error' | 'success'; onClose: () => void }) => (
+ <motion.div
+ initial={{ opacity: 0, y: -20 }}
+ animate={{ opacity: 1, y: 0 }}
+ exit={{ opacity: 0, y: -20 }}
+ className={`fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg ${
+ type === 'error'
+ ? 'bg-[#FF6A88]/10 border border-[#FF6A88]/20 text-[#FF6A88]'
+ : 'bg-[#6ADBFF]/10 border border-[#6ADBFF]/20 text-[#6ADBFF]'
+ }`}
+ >
+ <div className="flex items-center space-x-2">
+ <span className="text-sm">{message}</span>
+ <button onClick={onClose} className="ml-2 text-sm hover:opacity-80">脳</button>
+ </div>
+ </motion.div>
+);
export default function RegisterPage() {
const [username, setUsername] = useState('');
@@ -11,8 +32,17 @@
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
+ const [isSuccess, setIsSuccess] = useState(false);
const [mounted, setMounted] = useState(false);
const [particles, setParticles] = useState<Array<{x: number, y: number}>>([]);
+ const [toast, setToast] = useState<{ message: string; type: 'error' | 'success' } | null>(null);
+ const [errors, setErrors] = useState<{
+ username?: string;
+ accountName?: string;
+ password?: string;
+ confirmPassword?: string;
+ }>({});
+ const router = useRouter();
// 纭繚缁勪欢鎸傝浇鍚庡啀鏄剧ず鍔ㄧ敾鏁堟灉鍜岀敓鎴愮矑瀛�
useEffect(() => {
@@ -34,19 +64,98 @@
);
}
- const handleSubmit = (e: React.FormEvent) => {
+ const showToast = (message: string, type: 'error' | 'success') => {
+ setToast({ message, type });
+ setTimeout(() => setToast(null), 3000);
+ };
+
+ const validateForm = () => {
+ const newErrors: typeof errors = {};
+ let isValid = true;
+
+ if (!username) {
+ newErrors.username = '鐢ㄦ埛鏄电О涓嶈兘涓虹┖';
+ isValid = false;
+ }
+
+ if (!accountName) {
+ newErrors.accountName = '璐﹀彿鍚嶄笉鑳戒负绌�';
+ isValid = false;
+ } else if (!/^[a-zA-Z0-9_]{4,16}$/.test(accountName)) {
+ newErrors.accountName = '璐﹀彿鍚嶅繀椤绘槸4-16浣嶅瓧姣嶃�佹暟瀛楁垨涓嬪垝绾�';
+ isValid = false;
+ }
+
+ if (!password) {
+ newErrors.password = '瀵嗙爜涓嶈兘涓虹┖';
+ isValid = false;
+ } else if (!/^[a-zA-Z0-9_]{6,16}$/.test(password)) {
+ newErrors.password = '瀵嗙爜蹇呴』鏄�6-16浣嶅瓧姣嶃�佹暟瀛楁垨涓嬪垝绾�';
+ isValid = false;
+ }
+
+ if (!confirmPassword) {
+ newErrors.confirmPassword = '璇风‘璁ゅ瘑鐮�';
+ isValid = false;
+ } else if (password !== confirmPassword) {
+ newErrors.confirmPassword = '涓ゆ杈撳叆鐨勫瘑鐮佷笉涓�鑷�';
+ isValid = false;
+ }
+
+ setErrors(newErrors);
+ return isValid;
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
+
+ if (!validateForm()) {
+ return;
+ }
+
setIsLoading(true);
- // 妯℃嫙娉ㄥ唽璇锋眰
- setTimeout(() => {
- setIsLoading(false);
- // 杩欓噷搴旇娣诲姞瀹為檯娉ㄥ唽閫昏緫
- }, 2000);
+ try {
+ const response = await ApiService.post('/users/register', {
+ nickname: username,
+ accountName,
+ password
+ });
+
+ if (response.code === 200) {
+ setIsSuccess(true);
+ showToast('娉ㄥ唽鎴愬姛锛佹鍦ㄨ烦杞埌鐧诲綍椤甸潰...', 'success');
+ setTimeout(() => {
+ router.push('/login');
+ }, 1500);
+ } else {
+ setErrors({
+ accountName: response.message || '娉ㄥ唽澶辫触锛岃閲嶈瘯'
+ });
+ }
+ } catch (err) {
+ setErrors({
+ accountName: '缃戠粶閿欒锛岃绋嶅悗閲嶈瘯'
+ });
+ } finally {
+ if (!isSuccess) {
+ setIsLoading(false);
+ }
+ }
};
return (
<div className="h-screen w-full flex items-center justify-center relative overflow-hidden">
+ <AnimatePresence>
+ {toast && (
+ <Toast
+ message={toast.message}
+ type={toast.type}
+ onClose={() => setToast(null)}
+ />
+ )}
+ </AnimatePresence>
+
{/* 鑳屾櫙鏁堟灉 */}
<div className="fixed inset-0 bg-gradient-to-br from-[#0A1033] via-[#1E2B63] to-[#131C41] z-0"></div>
@@ -289,13 +398,21 @@
id="username"
name="username"
type="text"
- required
value={username}
- onChange={(e) => setUsername(e.target.value)}
- className="block w-full px-4 py-2 bg-[#131C41]/80 border border-[#6ADBFF]/30 rounded-md focus:outline-none focus:ring-1 focus:ring-[#6ADBFF]/60 focus:border-[#6ADBFF]/60 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300"
- placeholder="鐢ㄦ埛鍚�"
+ onChange={(e) => {
+ setUsername(e.target.value);
+ setErrors(prev => ({ ...prev, username: undefined }));
+ }}
+ className={`block w-full px-4 py-2 bg-[#131C41]/80 border ${
+ errors.username
+ ? 'border-[#FF6A88] focus:border-[#FF6A88] focus:ring-[#FF6A88]/50'
+ : 'border-[#6ADBFF]/30 focus:border-[#6ADBFF]/60 focus:ring-[#6ADBFF]/50'
+ } rounded-md focus:outline-none focus:ring-1 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300`}
+ placeholder="鐢ㄦ埛鏄电О"
/>
- <div className="absolute bottom-0 left-0 w-full h-[1px] bg-gradient-to-r from-[#6ADBFF] to-transparent scale-x-0 origin-left transition-transform duration-300 ease-out group-focus-within:scale-x-100"></div>
+ {errors.username && (
+ <p className="mt-1 text-xs text-[#FF6A88]">{errors.username}</p>
+ )}
</motion.div>
<motion.div
@@ -308,13 +425,21 @@
id="accountName"
name="accountName"
type="text"
- required
value={accountName}
- onChange={(e) => setAccountName(e.target.value)}
- className="block w-full px-4 py-2 bg-[#131C41]/80 border border-[#6ADBFF]/30 rounded-md focus:outline-none focus:ring-1 focus:ring-[#6ADBFF]/60 focus:border-[#6ADBFF]/60 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300"
- placeholder="璐﹀彿鍚�"
+ onChange={(e) => {
+ setAccountName(e.target.value);
+ setErrors(prev => ({ ...prev, accountName: undefined }));
+ }}
+ className={`block w-full px-4 py-2 bg-[#131C41]/80 border ${
+ errors.accountName
+ ? 'border-[#FF6A88] focus:border-[#FF6A88] focus:ring-[#FF6A88]/50'
+ : 'border-[#6ADBFF]/30 focus:border-[#6ADBFF]/60 focus:ring-[#6ADBFF]/50'
+ } rounded-md focus:outline-none focus:ring-1 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300`}
+ placeholder="璐﹀彿鍚嶏紙4-16浣嶅瓧姣嶃�佹暟瀛楁垨涓嬪垝绾匡級"
/>
- <div className="absolute bottom-0 left-0 w-full h-[1px] bg-gradient-to-r from-[#6ADBFF] to-transparent scale-x-0 origin-left transition-transform duration-300 ease-out group-focus-within:scale-x-100"></div>
+ {errors.accountName && (
+ <p className="mt-1 text-xs text-[#FF6A88]">{errors.accountName}</p>
+ )}
</motion.div>
<motion.div
@@ -327,13 +452,21 @@
id="password"
name="password"
type="password"
- required
value={password}
- onChange={(e) => setPassword(e.target.value)}
- className="block w-full px-4 py-2 bg-[#131C41]/80 border border-[#6ADBFF]/30 rounded-md focus:outline-none focus:ring-1 focus:ring-[#6ADBFF]/60 focus:border-[#6ADBFF]/60 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300"
- placeholder="瀵嗙爜"
+ onChange={(e) => {
+ setPassword(e.target.value);
+ setErrors(prev => ({ ...prev, password: undefined }));
+ }}
+ className={`block w-full px-4 py-2 bg-[#131C41]/80 border ${
+ errors.password
+ ? 'border-[#FF6A88] focus:border-[#FF6A88] focus:ring-[#FF6A88]/50'
+ : 'border-[#6ADBFF]/30 focus:border-[#6ADBFF]/60 focus:ring-[#6ADBFF]/50'
+ } rounded-md focus:outline-none focus:ring-1 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300`}
+ placeholder="瀵嗙爜锛�6-16浣嶅瓧姣嶃�佹暟瀛楁垨涓嬪垝绾匡級"
/>
- <div className="absolute bottom-0 left-0 w-full h-[1px] bg-gradient-to-r from-[#6ADBFF] to-transparent scale-x-0 origin-left transition-transform duration-300 ease-out group-focus-within:scale-x-100"></div>
+ {errors.password && (
+ <p className="mt-1 text-xs text-[#FF6A88]">{errors.password}</p>
+ )}
</motion.div>
<motion.div
@@ -346,13 +479,21 @@
id="confirmPassword"
name="confirmPassword"
type="password"
- required
value={confirmPassword}
- onChange={(e) => setConfirmPassword(e.target.value)}
- className="block w-full px-4 py-2 bg-[#131C41]/80 border border-[#6ADBFF]/30 rounded-md focus:outline-none focus:ring-1 focus:ring-[#6ADBFF]/60 focus:border-[#6ADBFF]/60 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300"
+ onChange={(e) => {
+ setConfirmPassword(e.target.value);
+ setErrors(prev => ({ ...prev, confirmPassword: undefined }));
+ }}
+ className={`block w-full px-4 py-2 bg-[#131C41]/80 border ${
+ errors.confirmPassword
+ ? 'border-[#FF6A88] focus:border-[#FF6A88] focus:ring-[#FF6A88]/50'
+ : 'border-[#6ADBFF]/30 focus:border-[#6ADBFF]/60 focus:ring-[#6ADBFF]/50'
+ } rounded-md focus:outline-none focus:ring-1 text-white text-xs placeholder-[#6ADBFF]/50 transition-all duration-300`}
placeholder="纭瀵嗙爜"
/>
- <div className="absolute bottom-0 left-0 w-full h-[1px] bg-gradient-to-r from-[#6ADBFF] to-transparent scale-x-0 origin-left transition-transform duration-300 ease-out group-focus-within:scale-x-100"></div>
+ {errors.confirmPassword && (
+ <p className="mt-1 text-xs text-[#FF6A88]">{errors.confirmPassword}</p>
+ )}
</motion.div>
<motion.div
@@ -362,11 +503,18 @@
>
<button
type="submit"
- disabled={isLoading}
- className="relative w-full py-2 px-4 rounded-full overflow-hidden border border-[#6ADBFF]/40 bg-gradient-to-r from-[#131C41] to-[#1E2B63] hover:border-[#6ADBFF]/70 transition-all duration-300 group quantum-button"
+ disabled={isLoading || isSuccess}
+ className="relative w-full py-2 px-4 rounded-full overflow-hidden border border-[#6ADBFF]/40 bg-gradient-to-r from-[#131C41] to-[#1E2B63] hover:border-[#6ADBFF]/70 transition-all duration-300 group quantum-button disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="relative z-10 flex items-center justify-center quantum-pulse">
- {isLoading ? (
+ {isSuccess ? (
+ <>
+ <svg className="w-4 h-4 text-[#6ADBFF]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
+ </svg>
+ <span className="ml-2 text-xs text-white">娉ㄥ唽鎴愬姛锛屾鍦ㄨ烦杞�...</span>
+ </>
+ ) : isLoading ? (
<>
<div className="w-3 h-3 border-2 border-white/80 border-t-transparent rounded-full animate-spin"></div>
<span className="ml-2 text-xs text-white">娉ㄥ唽涓�...</span>
--
Gitblit v1.9.3