hongjli
2025-04-16 25d10113c4d27ccdea506e352b0bdd194560b0dc
注册
已修改1个文件
206 ■■■■ 文件已修改
src/app/register/page.tsx 206 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>