hongjli
2025-04-16 bce8e3bf66c00ef6fb32f26c27c39b5ac3b4d47f
登录
已修改3个文件
已添加2个文件
222 ■■■■■ 文件已修改
src/app/globals.css 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/layout.tsx 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/login/page.tsx 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/context/UserContext.tsx 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/api.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/globals.css
@@ -806,3 +806,16 @@
.animate-shine {
  animation: shine 1.5s ease-in-out;
}
@keyframes error-scan {
  0% {
    transform: translateX(0%);
  }
  100% {
    transform: translateX(50%);
  }
}
.animate-error-scan {
  animation: error-scan 2s linear infinite;
}
src/app/layout.tsx
@@ -3,6 +3,7 @@
import "./globals.css";
import ClientLayoutContent from '@/components/layout/ClientLayoutContent';
import Script from 'next/script';
import { UserProvider } from "@/context/UserContext";
const inter = Inter({ subsets: ["latin"] });
@@ -39,7 +40,9 @@
    <html lang="zh-CN" className="smooth-scroll">
      <ScrollToTop />
      <body className={`${inter.className} overflow-x-hidden`}>
        <ClientLayoutContent>{children}</ClientLayoutContent>
        <UserProvider>
          <ClientLayoutContent>{children}</ClientLayoutContent>
        </UserProvider>
      </body>
    </html>
  );
src/app/login/page.tsx
@@ -4,6 +4,17 @@
import Image from 'next/image';
import { motion } from 'framer-motion';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useUser } from '@/context/UserContext';
import ApiService from '@/utils/api';
// å®šä¹‰User接口
interface User {
  id: number;
  username: string;
  email: string;
  // å…¶ä»–用户属性...
}
export default function LoginPage() {
  const [email, setEmail] = useState('');
@@ -13,21 +24,54 @@
  const [isLoading, setIsLoading] = useState(false);
  const [mounted, setMounted] = useState(false);
  const [loginMethod, setLoginMethod] = useState<'password' | 'sms'>('password');
  const [error, setError] = useState('');
  const router = useRouter();
  const { setUser } = useUser();
  // ç¡®ä¿ç»„件挂载后再显示动画效果
  useEffect(() => {
    setMounted(true);
  }, []);
  
  const handleSubmit = (e: React.FormEvent) => {
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);
    setError(''); // æ¸…除之前的错误信息
    
    // æ¨¡æ‹Ÿç™»å½•请求
    setTimeout(() => {
    try {
      const response = await ApiService.post<string>('/users/login', {
        accountName: email,
        password,
      });
      if (response.code === 200) {
        // èŽ·å–token
        const token = response.data;
        // ä½¿ç”¨ApiService的方法设置token
        ApiService.setToken(token);
        // ä½¿ç”¨æ–°token获取用户信息
        try {
          const userData = await ApiService.get<User>('/users/info', token);
          if (userData.code === 200) {
            // ä¿å­˜ç”¨æˆ·ä¿¡æ¯åˆ°å…¨å±€çŠ¶æ€
            setUser(userData.data);
            router.push('/'); // ç™»å½•成功后跳转到首页
          } else {
            setError('获取用户信息失败');
          }
        } catch (error) {
          setError('获取用户信息失败');
        }
      } else {
        setError(response.message || '登录失败,请检查账号密码');
      }
    } catch (err) {
      setError('网络错误,请稍后重试');
    } finally {
      setIsLoading(false);
      // è¿™é‡Œåº”该添加实际登录逻辑
    }, 2000);
    }
  };
  return (
@@ -239,6 +283,29 @@
          
          {/* è´¦å·ç™»å½•表单 */}
          <form onSubmit={handleSubmit} className="space-y-4">
            {error && (
              <motion.div
                initial={{ opacity: 0, y: -10 }}
                animate={{ opacity: 1, y: 0 }}
                className="relative overflow-hidden backdrop-blur-sm bg-[#FF6A88]/5 border border-[#FF6A88]/20 rounded-lg p-3"
              >
                <div className="absolute top-0 left-0 right-0 h-[1px] bg-gradient-to-r from-transparent via-[#FF6A88]/50 to-transparent"></div>
                <div className="absolute bottom-0 left-0 right-0 h-[1px] bg-gradient-to-r from-transparent via-[#FF6A88]/50 to-transparent"></div>
                <div className="absolute top-0 bottom-0 left-0 w-[1px] bg-gradient-to-b from-transparent via-[#FF6A88]/50 to-transparent"></div>
                <div className="absolute top-0 bottom-0 right-0 w-[1px] bg-gradient-to-b from-transparent via-[#FF6A88]/50 to-transparent"></div>
                <div className="flex items-center space-x-2">
                  <svg className="w-4 h-4 text-[#FF6A88]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
                  </svg>
                  <span className="text-xs text-[#FF6A88]">{error}</span>
                </div>
                {/* åŠ¨æ€æ‰«æçº¿ */}
                <div className="absolute top-0 -left-full w-[200%] h-full bg-gradient-to-r from-transparent via-[#FF6A88]/10 to-transparent animate-error-scan"></div>
              </motion.div>
            )}
            <div className="space-y-3">
              {loginMethod === 'password' ? (
                <>
src/context/UserContext.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
'use client';
import React, { createContext, useContext, useState, useEffect } from 'react';
import ApiService from '@/utils/api';
interface User {
  id: number;
  username: string;
  email: string;
  // æ ¹æ®å®žé™…用户信息结构添加其他字段
}
interface UserContextType {
  user: User | null;
  setUser: (user: User | null) => void;
  isLoading: boolean;
  logout: () => void;
}
const UserContext = createContext<UserContextType | undefined>(undefined);
export function UserProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  useEffect(() => {
    const initializeUser = async () => {
      const token = localStorage.getItem('token');
      if (token) {
        try {
          const response = await ApiService.get<User>('/users/info');
          if (response.code === 200) {
            setUser(response.data);
          }
        } catch (error) {
          console.error('Failed to fetch user info:', error);
          localStorage.removeItem('token'); // å¦‚果获取用户信息失败,清除token
        }
      }
      setIsLoading(false);
    };
    initializeUser();
  }, []);
  const logout = () => {
    setUser(null);
    localStorage.removeItem('token');
  };
  return (
    <UserContext.Provider value={{ user, setUser, isLoading, logout }}>
      {children}
    </UserContext.Provider>
  );
}
export function useUser() {
  const context = useContext(UserContext);
  if (context === undefined) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
}
src/utils/api.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
const API_BASE_URL = 'http://localhost:8080/api';
interface ApiResponse<T = any> {
  code: number;
  message: string;
  data: T;
}
class ApiService {
  private static getToken(): string | null {
    if (typeof window !== 'undefined') {
      return localStorage.getItem('token');
    }
    return null;
  }
  static setToken(token: string): void {
    if (typeof window !== 'undefined') {
      localStorage.setItem('token', token);
    }
  }
  private static async fetchWithToken(url: string, options: RequestInit = {}, customToken?: string): Promise<ApiResponse> {
    const token = customToken || this.getToken();
    const headers = {
      'Content-Type': 'application/json',
      ...(token ? { 'token': token } : {}),
      ...options.headers,
    };
    const response = await fetch(`${API_BASE_URL}${url}`, {
      ...options,
      headers,
    });
    const data = await response.json();
    return data;
  }
  static async get<T>(url: string, customToken?: string): Promise<ApiResponse<T>> {
    return this.fetchWithToken(url, { method: 'GET' }, customToken);
  }
  static async post<T>(url: string, body: any, customToken?: string): Promise<ApiResponse<T>> {
    return this.fetchWithToken(url, {
      method: 'POST',
      body: JSON.stringify(body),
    }, customToken);
  }
  static async put<T>(url: string, body: any, customToken?: string): Promise<ApiResponse<T>> {
    return this.fetchWithToken(url, {
      method: 'PUT',
      body: JSON.stringify(body),
    }, customToken);
  }
  static async delete<T>(url: string, customToken?: string): Promise<ApiResponse<T>> {
    return this.fetchWithToken(url, { method: 'DELETE' }, customToken);
  }
}
export default ApiService;