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