/** * filekorolheader: 登录表单组件 - 用户密码登录和手机号登录功能 * 功能:密码登录、手机号登录、表单验证、验证码处理 * 路径:/login/components/LoginForm.tsx * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn/ui语义化样式,支持暗色主题 */ 'use client'; import React, { useEffect, useState } from 'react'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { CaptchaInput } from '@/components/auth/CaptchaInput'; import { Lock, User, Phone, MessageSquare, Eye, EyeOff, LogIn, Loader2, Smartphone, } from 'lucide-react'; import { toast } from 'sonner'; import { useAuth } from '@/components/auth/AuthContext'; import { authReducer, initialAuthState } from './authReducer'; import { loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen'; import type { CaptchaResponse } from '@/lib/api/types.gen'; import {PERSONAL_CELTRAL_PAGE} from "@/config/constants" interface LoginFormProps { onRegisterClick: () => void; } export function LoginForm({ onRegisterClick }: LoginFormProps) { const { login } = useAuth(); const [state, dispatch] = React.useReducer(authReducer, initialAuthState); const [loginType, setLoginType] = useState<'password' | 'phone'>('password'); const [passwordCaptchaData, setPasswordCaptchaData] = useState(null); // 倒计时效果 useEffect(() => { if (state.countdown > 0) { const timer = setTimeout(() => { dispatch({ type: 'SET_COUNTDOWN', payload: state.countdown - 1 }); }, 1000); return () => clearTimeout(timer); } }, [state.countdown]); // 发送验证码 const handleSendCode = async () => { if (!state.phoneForm.phone) { toast.error('请输入手机号'); return; } if (!/^1[3-9]\d{9}$/.test(state.phoneForm.phone)) { toast.error('请输入正确的手机号'); return; } dispatch({ type: 'SET_SENDING_CODE', payload: true }); dispatch({ type: 'SET_ERROR', payload: '' }); try { // 模拟发送验证码 await new Promise(resolve => setTimeout(resolve, 1000)); toast.success('验证码发送成功!(测试验证码:123456)'); dispatch({ type: 'SET_COUNTDOWN', payload: 60 }); } catch (err) { dispatch({ type: 'SET_ERROR', payload: '发送验证码失败,请稍后重试' }); toast.error('发送验证码失败'); } finally { dispatch({ type: 'SET_SENDING_CODE', payload: false }); } }; // 密码登录 const handlePasswordLogin = async (e: React.FormEvent) => { e.preventDefault(); dispatch({ type: 'SET_ERROR', payload: '' }); if (!state.passwordForm.username || !state.passwordForm.password) { dispatch({ type: 'SET_ERROR', payload: '请输入用户名和密码' }); toast.error('请输入用户名和密码'); return; } if (!state.passwordForm.captcha) { dispatch({ type: 'SET_ERROR', payload: '请输入图形验证码' }); toast.error('请输入图形验证码'); return; } if (!passwordCaptchaData?.captcha_id) { dispatch({ type: 'SET_ERROR', payload: '验证码数据未获取,请刷新验证码' }); toast.error('验证码数据未获取,请刷新验证码'); return; } dispatch({ type: 'SET_LOADING', payload: true }); try { const response = await loginApiV1AuthLoginPost({ body: { identifier: state.passwordForm.username, password: state.passwordForm.password, captcha_id: passwordCaptchaData.captcha_id, captcha_text: state.passwordForm.captcha, }, }); if (response.data) { // 登录成功,提取用户信息 const userData = { id: response.data.user_id || '1', username: state.passwordForm.username, realName: response.data.real_name || state.passwordForm.username, phone: response.data.phone || '', email: response.data.email || '', role: response.data.role || 'user', permissions: response.data.permissions || [], enterpriseId: response.data.enterprise_id || '', enterpriseName: response.data.enterprise_name || '', createdAt: response.data.created_at || new Date().toISOString(), // 重要:存储token到用户对象中 token: response.data.access_token || response.data.token || null, refreshToken:response.data.refresh_token || '' }; // 打印登录成功日志 console.log('🎉 登录成功!', { user: userData, apiResponse: response.data, timestamp: new Date().toISOString() }); // 验证token是否正确存储 if (userData.token) { console.log('🔑 Token已存储:', userData.token.substring(0, 20) + '...'); } else { console.warn('⚠️ 未找到token,请检查API响应格式'); } login(userData); toast.success('登录成功!正在跳转...'); // 跳转到个人中心页面 window.location.href = PERSONAL_CELTRAL_PAGE; } else { dispatch({ type: 'SET_ERROR', payload: '登录失败,请检查用户名和密码' }); toast.error('登录失败,请检查用户名和密码'); } } catch (error: unknown) { console.error('????:', error); const apiMessage = typeof error === 'object' && error !== null && 'response' in error ? (error as { response?: { data?: { message?: string } } }).response?.data?.message : undefined; const fallbackMessage = error instanceof Error ? error.message : undefined; const errorMessage = apiMessage || fallbackMessage || '??????????'; dispatch({ type: 'SET_ERROR', payload: errorMessage }); toast.error(errorMessage); } finally { dispatch({ type: 'SET_LOADING', payload: false }); } }; // 手机号登录 const handlePhoneLogin = async (e: React.FormEvent) => { e.preventDefault(); dispatch({ type: 'SET_ERROR', payload: '' }); if (!state.phoneForm.phone || !state.phoneForm.code) { dispatch({ type: 'SET_ERROR', payload: '请输入手机号和验证码' }); return; } if (!state.phoneForm.captcha) { dispatch({ type: 'SET_ERROR', payload: '请输入图形验证码' }); return; } dispatch({ type: 'SET_LOADING', payload: true }); try { // 模拟手机号登录 await new Promise(resolve => setTimeout(resolve, 1000)); if (state.phoneForm.code === '123456') { login({ id: '2', username: 'user_' + state.phoneForm.phone.slice(-4), realName: '用户', phone: state.phoneForm.phone, email: '', role: 'user', permissions: [], enterpriseId: '', enterpriseName: '', createdAt: new Date().toISOString(), }); toast.success('登录成功!'); window.location.href = '/'; } else { dispatch({ type: 'SET_ERROR', payload: '验证码错误' }); toast.error('验证码错误'); } } catch (error) { console.error('???????:', error); dispatch({ type: 'SET_ERROR', payload: '??????????' }); toast.error('????'); } finally { dispatch({ type: 'SET_LOADING', payload: false }); } }; return ( setLoginType(v as 'password' | 'phone')}> 密码登录 手机登录 {state.error && (

{state.error}

)} {/* 密码登录 */}
dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: { username: e.target.value } })} className="pl-10" disabled={state.loading} />
dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: { password: e.target.value } })} className="pl-10 pr-10" disabled={state.loading} />
dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: { captcha: value } })} onCaptchaChange={(captchaData) => setPasswordCaptchaData(captchaData)} instanceId="password-login" className="mt-2" />
{/* 手机号登录 */}
dispatch({ type: 'UPDATE_PHONE_FORM', payload: { phone: e.target.value } })} className="pl-10" maxLength={11} disabled={state.loading} />
dispatch({ type: 'UPDATE_PHONE_FORM', payload: { code: e.target.value } })} className="pl-10" maxLength={6} disabled={state.loading} />
dispatch({ type: 'UPDATE_PHONE_FORM', payload: { captcha: value } })} instanceId="phone-login" className="mt-2" />
{/* 注册跳转 */}

还没有账号?

); }