@@ -22,6 +22,9 @@ const LoginPage: React.FC = () => {
const [ showSliderVerify , setShowSliderVerify ] = useState ( false ) ;
const [ sliderVerified , setSliderVerified ] = useState ( false ) ;
const [ loginSliderVerified , setLoginSliderVerified ] = useState ( false ) ;
const [ sliderKey , setSliderKey ] = useState ( 0 ) ;
const [ showResend , setShowResend ] = useState ( false ) ;
const [ loginShowResend , setLoginShowResend ] = useState ( false ) ;
const [ siteName , setSiteName ] = useState ( 'VideoGen.AI' ) ;
const [ siteLogo , setSiteLogo ] = useState ( '' ) ;
const [ agreementUrl , setAgreementUrl ] = useState ( '' ) ;
@@ -53,15 +56,16 @@ const LoginPage: React.FC = () => {
if ( ! checkAgreed ( ) ) return ;
try {
const values = await pwdForm . validateFields ( ) ;
setLoading ( true ) ;
// setLoading(true);
await login ( values . phone , values . password , undefined , values . rememberMe ) ;
message . success ( '登录成功,欢迎回来' ) ;
await checkAuth ( ) ;
navigate ( '/projects' ) ;
} catch {
message . error ( '登录失败' ) ;
} catch ( error : any ) {
const errorMsg = error ? . response ? . data ? . detail || error ? . response ? . data ? . message || error ? . message || '登录失败' ;
message . error ( errorMsg ) ;
} finally {
setLoading ( false ) ;
// setLoading(false);
}
} ;
@@ -78,8 +82,9 @@ const LoginPage: React.FC = () => {
message . success ( '登录成功,欢迎回来' ) ;
await checkAuth ( ) ;
navigate ( '/projects' ) ;
} catch {
message . error ( '登录失败' ) ;
} catch ( error : any ) {
const errorMsg = error ? . response ? . data ? . detail || error ? . response ? . data ? . message || error ? . message || '登录失败' ;
message . error ( errorMsg ) ;
} finally {
setLoading ( false ) ;
}
@@ -98,17 +103,23 @@ const LoginPage: React.FC = () => {
const errorMsg = error ? . response ? . data ? . detail || error ? . response ? . data ? . message || error ? . message || '注册失败' ;
message . error ( errorMsg ) ;
} finally {
// setLoading(false);
setLoading ( false ) ;
}
} ;
const startCountdown = ( setter : React.Dispatch < React.SetStateAction < number > > ) = > {
const startCountdown = ( setter : React.Dispatch < React.SetStateAction < number > > , isReg? : boolean ) = > {
setter ( 60 ) ;
const timer = setInterval ( ( ) = > {
setter ( ( c ) = > {
if ( c <= 1 ) {
clearInterval ( timer ) ;
setShowSliderVerify ( false ) ;
// 倒计时结束后,设置重新发送状态
if ( isReg ) {
setShowResend ( true ) ;
} else {
setLoginShowResend ( true ) ;
}
return 0 ;
}
return c - 1 ;
@@ -154,8 +165,19 @@ const LoginPage: React.FC = () => {
await sendSms ( phone , mode2 ) ;
startCountdown ( isReg ? setRegCountdown : setCountdown ) ;
message . success ( '验证码已发送' ) ;
} catch {
message . error ( '发送 失败') ;
} catch ( error : any ) {
const errorMsg = error ? . response ? . data ? . detail || error ? . response ? . data ? . message || error ? . message || '登录 失败';
message . error ( errorMsg ) ;
// 发送失败,重置验证状态、倒计时和滑块组件
if ( mode === 'register' ) {
setSliderVerified ( false ) ;
setRegCountdown ( 0 ) ;
} else {
setLoginSliderVerified ( false ) ;
setCountdown ( 0 ) ;
}
// 通过更新 key 强制刷新滑块组件
setSliderKey ( prev = > prev + 1 ) ;
}
} ;
@@ -177,20 +199,27 @@ const LoginPage: React.FC = () => {
// 设置对应的验证状态
if ( mode === 'register' ) {
setSliderVerified ( true ) ;
startCountdown ( setRegCountdown ) ;
startCountdown ( setRegCountdown , true ) ;
} else {
setLoginSliderVerified ( true ) ;
startCountdown ( setCountdown ) ;
startCountdown ( setCountdown , false ) ;
}
message . success ( '验证码已发送' ) ;
} catch {
message . error ( '发送 失败') ;
// 发送失败,重置滑动验证组件
} catch ( error : any ) {
const errorMsg = error ? . response ? . data ? . detail || error ? . response ? . data ? . message || error ? . message || '登录 失败';
message . error ( errorMsg ) ;
// 发送失败,重置验证状态、倒计时和滑块组件
if ( mode === 'register' ) {
setSliderVerified ( false ) ;
setRegCountdown ( 0 ) ;
setShowResend ( true ) ;
} else {
setLoginSliderVerified ( false ) ;
setCountdown ( 0 ) ;
setLoginShowResend ( true ) ;
}
// 通过更新 key 强制刷新滑块组件
setSliderKey ( prev = > prev + 1 ) ;
}
} ;
@@ -198,8 +227,20 @@ const LoginPage: React.FC = () => {
setTab ( t ) ;
setMode ( t ) ;
setCountdown ( 0 ) ;
setRegCountdown ( 0 ) ;
setLoginSliderVerified ( false ) ;
setSliderVerified ( false ) ;
setShowSliderVerify ( false ) ;
setShowResend ( false ) ;
setLoginShowResend ( false ) ;
// 刷新滑动验证组件
setSliderKey ( prev = > prev + 1 ) ;
// 清空当前模式相关的表单
if ( t === 'password' ) {
pwdForm . resetFields ( ) ;
} else {
phoneForm . resetFields ( ) ;
}
} ;
const features = [
@@ -338,7 +379,7 @@ const LoginPage: React.FC = () => {
{ /* Password Login */ }
{ mode === 'password' && (
< Form form = { pwdForm } size = "large" layout = "vertical" >
< Form form = { pwdForm } size = "large" layout = "vertical" onFinish = { handlePasswordLogin } >
< Form.Item name = "phone" rules = { [ { required : true , message : '请输入手机号' } , { pattern : /^1\d{10}$/ , message : '请输入正确的手机号' } ] } >
< Input prefix = { < MobileOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > } placeholder = "请输入手机号" style = { inputStyle } / >
< / Form.Item >
@@ -349,7 +390,7 @@ const LoginPage: React.FC = () => {
< Checkbox > 记 住 我 的 登 录 状 态 < / Checkbox >
< / Form.Item >
< Form.Item style = { { marginBottom : 12 } } >
< Button type = "primary" onClick = { handlePasswordLogin } loading = { loading } block style = { {
< Button type = "primary" htmlType = "submit" loading = { loading } block style = { {
height : 48 , borderRadius : 10 , fontSize : 16 , fontWeight : 600 ,
background : 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)' ,
border : 'none' , boxShadow : '0 8px 24px rgba(99,102,241,0.25)' ,
@@ -366,30 +407,38 @@ const LoginPage: React.FC = () => {
< / Form.Item >
< Form.Item name = "code" rules = { [ { required : true , message : '请输入验证码' } ] } >
< Space.Compact style = { { width : '100%' } } >
< Input prefix = { < SafetyOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > } placeholder = "请输入验证码" maxLength = { 6 }
< Input
prefix = { < SafetyOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > }
placeholder = "请输入验证码"
maxLength = { 6 }
disabled = { ! loginSliderVerified }
style = { { . . . inputStyle , borderRadius : '10px 0 0 10px' , flex : 1 } } / >
< Button disabled = { countdown > 0 }
onClick = { ( ) = > {
if ( loginSliderVerified ) {
handleSliderSuccess ( true ) ;
if ( loginShowResend ) {
// 重新发送时,只刷新滑块,不启动倒计时
setLoginSliderVerified ( false ) ;
setShowSliderVerify ( true ) ;
setSliderKey ( prev = > prev + 1 ) ;
} else {
// 首次点击,调用发送验证码,等待滑块验证
handleSendCode ( phoneForm . getFieldValue ( 'phone' ) ) ;
}
} }
style = { {
height : 48 , borderRadius : '0 10px 10px 0' ,
background : countdown > 0 || loginSliderVerified ? '#f1f5f9' : 'rgba(99,102,241,0.1)' ,
background : countdown > 0 ? '#f1f5f9' : ( loginSliderVerified ? '#f1f5f9' : 'rgba(99,102,241,0.1)' ) ,
border : '1.5px solid #e2e8f0' , borderLeft : 'none' ,
color : countdown > 0 ? '#94a3b8' : '#6366f1' ,
fontWeight : 600 , minWidth : 100 ,
} } >
{ countdown > 0 ? ` ${ countdown } s ` : ( loginSliderVerified ? '重新发送' : '获取验证码' ) }
{ countdown > 0 ? ` ${ countdown } s ` : ( loginShowResend ? '重新发送' : '获取验证码' ) }
< / Button >
< / Space.Compact >
< / Form.Item >
{ /* 滑动验证 - 获取验证码后显示 */ }
{ showSliderVerify && (
< Form.Item style = { { marginBottom : 12 } } >
< Form.Item style = { { marginBottom : 12 } } key = { sliderKey } >
< SliderVerify
onSuccess = { handleSliderSuccess }
isVerified = { loginSliderVerified }
@@ -415,40 +464,49 @@ const LoginPage: React.FC = () => {
< / Form.Item >
< Form.Item name = "regCode" rules = { [ { required : true , message : '请输入验证码' } ] } >
< Space.Compact style = { { width : '100%' } } >
< Input prefix = { < SafetyOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > } placeholder = "请输入验证码" maxLength = { 6 }
< Input
prefix = { < SafetyOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > }
placeholder = "请输入验证码"
maxLength = { 6 }
disabled = { ! sliderVerified }
style = { { . . . inputStyle , borderRadius : '10px 0 0 10px' , flex : 1 } } / >
< Button disabled = { regCountdown > 0 }
onClick = { ( ) = > {
if ( sliderVerified ) {
handleSliderSuccess ( true ) ;
if ( showResend ) {
// 重新发送时,只刷新滑块,不启动倒计时
setSliderVerified ( false ) ;
setShowSliderVerify ( true ) ;
setSliderKey ( prev = > prev + 1 ) ;
} else {
// 首次点击,调用发送验证码,等待滑块验证
handleSendCode ( regForm . getFieldValue ( 'phone' ) , true ) ;
}
} }
style = { {
height : 48 , borderRadius : '0 10px 10px 0' ,
background : regCountdown > 0 || sliderVerified ? '#f1f5f9' : 'rgba(99,102,241,0.1)' ,
background : regCountdown > 0 ? '#f1f5f9' : ( sliderVerified ? '#f1f5f9' : 'rgba(99,102,241,0.1)' ) ,
border : '1.5px solid #e2e8f0' , borderLeft : 'none' ,
color : regCountdown > 0 ? '#94a3b8' : '#6366f1' ,
fontWeight : 600 , minWidth : 100 ,
} } >
{ regCountdown > 0 ? ` ${ regCountdown } s ` : ( sliderVerified ? '重新发送' : '获取验证码' ) }
{ regCountdown > 0 ? ` ${ regCountdown } s ` : ( showResend ? '重新发送' : '获取验证码' ) }
< / Button >
< / Space.Compact >
< / Form.Item >
< Form.Item name = "password" rules = { [ { required : true , message : '请设置密码' } , { min : 6 , message : '密码至少6位' } ] } >
< Input.Password prefix = { < LockOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > } placeholder = "请设置密码(至少6位)" style = { inputStyle } / >
< / Form.Item >
{ /* 滑动验证 - 获取验证码后显示 */ }
{ showSliderVerify && (
< Form.Item style = { { marginBottom : 12 } } >
< Form.Item style = { { marginBottom : 12 } } key = { sliderKey } >
< SliderVerify
onSuccess = { handleSliderSuccess }
isVerified = { sliderVerified }
/ >
< / Form.Item >
) }
< Form.Item name = "password" rules = { [ { required : true , message : '请设置密码' } , { min : 6 , message : '密码至少6位' } ] } >
< Input.Password prefix = { < LockOutlined style = { { color : '#94a3b8' , marginRight : 8 } } / > } placeholder = "请设置密码(至少6位)" style = { inputStyle } / >
< / Form.Item >
< Form.Item style = { { marginBottom : 12 } } >
< Button type = "primary" onClick = { handleRegister } loading = { loading } block style = { {
@@ -483,14 +541,30 @@ const LoginPage: React.FC = () => {
{ mode === 'register' ? (
< Typography.Text
style = { { fontSize : 13 , color : '#6366f1' , cursor : 'pointer' } }
onClick = { ( ) = > { setMode ( 'password' ) ; setTab ( 'password' ) ; } }
onClick = { ( ) = > {
setMode ( 'password' ) ;
setTab ( 'password' ) ;
setShowResend ( false ) ;
setLoginShowResend ( false ) ;
setSliderVerified ( false ) ;
setShowSliderVerify ( false ) ;
setSliderKey ( prev = > prev + 1 ) ;
regForm . resetFields ( ) ;
} }
>
已 有 账 号 ? 去 登 录
< / Typography.Text >
) : (
< Typography.Text
style = { { fontSize : 13 , color : '#6366f1' , cursor : 'pointer' } }
onClick = { ( ) = > { setMode ( 'register' ) ; } }
onClick = { ( ) = > {
setMode ( 'register' ) ;
setShowResend ( false ) ;
setLoginShowResend ( false ) ;
setSliderVerified ( false ) ;
setShowSliderVerify ( false ) ;
setSliderKey ( prev = > prev + 1 ) ;
} }
>
没 有 账 号 ? 立 即 注 册
< / Typography.Text >
@@ -513,10 +587,36 @@ const SliderVerify: React.FC<{
const positionRef = React . useRef ( 0 ) ;
const successRef = React . useRef ( false ) ; // 防止重复触发成功回调
const containerWidth = 360 ; // 容器宽度
const sliderWidth = 60 ; // 滑块宽度
const [ containerWidth , setContainerWidth ] = React . useState ( 360 ) ; // 容器宽度,自适应
const sliderWidth = 50 ; // 滑块宽度
const maxPosition = containerWidth - sliderWidth ;
// 监听容器尺寸变化,使其自适应父容器宽度
React . useEffect ( ( ) = > {
const updateWidth = ( ) = > {
if ( containerRef . current ) {
const width = containerRef . current . offsetWidth ;
if ( width > 0 ) {
setContainerWidth ( width ) ;
}
}
} ;
updateWidth ( ) ;
const resizeObserver = new ResizeObserver ( updateWidth ) ;
if ( containerRef . current ) {
resizeObserver . observe ( containerRef . current ) ;
}
window . addEventListener ( 'resize' , updateWidth ) ;
return ( ) = > {
resizeObserver . disconnect ( ) ;
window . removeEventListener ( 'resize' , updateWidth ) ;
} ;
} , [ ] ) ;
const updatePosition = ( x : number ) = > {
// 如果已经成功,不再处理
if ( successRef . current || isVerified ) return ;
@@ -591,28 +691,36 @@ const SliderVerify: React.FC<{
ref = { containerRef }
onMouseDown = { handleMouseDown }
style = { {
width : containerWidth ,
height : 44 ,
background : '#f8fafc ' ,
borderRadius : 8 ,
width : '100%' ,
height : 50 ,
background : '#ffffff ' ,
borderRadius : 25 ,
position : 'relative' ,
overflow : 'hidden' ,
border : '1px solid #e2e8f0 ' ,
border : '1px solid rgba(99, 102, 241, 0.1) ' ,
boxShadow : 'inset 0 2px 6px rgba(148, 163, 184, 0.15), 0 4px 12px rgba(0, 0, 0, 0.04)' ,
cursor : isVerified ? 'default' : 'pointer' ,
userSelect : 'none' ,
touchAction : 'none' ,
} }
>
{ /* 已滑动部分背景 */ }
{ /* 已滑动部分背景 - 包含阴影弧度 */ }
< div
ref = { trackRef }
style = { {
position : 'absolute' ,
left : 0 ,
left : - 2 ,
top : 0 ,
bottom : 0 ,
width : isVerified ? containerWidth : sliderWidth ,
background : isVerified ? '#dcfce7' : '#e0e7ff' ,
width : isVerified ? containerWidth + 4 : sliderWidth ,
background : isVerified
? 'linear-gradient(90deg, #dcfce7 0%, #bbf7d0 100%)'
: 'linear-gradient(90deg, rgba(224, 231, 255, 0.6) 0%, rgba(199, 210, 254, 0.8) 100%)' ,
borderRadius : isVerified ? 0 : '0 25px 25px 0' ,
boxShadow : isVerified
? 'none'
: '4px 0 12px rgba(99, 102, 241, 0.2), 2px 0 4px rgba(99, 102, 241, 0.1)' ,
transition : successRef.current ? 'all 0.3s ease' : 'none' ,
} }
/ >
@@ -632,14 +740,15 @@ const SliderVerify: React.FC<{
color : isVerified ? '#16a34a' : '#64748b' ,
zIndex : 1 ,
pointerEvents : 'none' ,
letterSpacing : 0.5 ,
} }
>
{ isVerified ? (
< span style = { { display : 'flex' , alignItems : 'center' , gap : 6 } } >
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2.5 " strokeLinecap = "round" strokeLinejoin = "round" >
< polyline points = "20 6 9 17 4 12" > < / polyline >
< / svg >
已 发 送
验 证 成 功
< / span >
) : (
'滑动发送'
@@ -652,28 +761,32 @@ const SliderVerify: React.FC<{
style = { {
position : 'absolute' ,
left : isVerified ? maxPosition : 0 ,
top : 4 ,
width : sliderWidth - 8 ,
height : 36 ,
background : isVerified ? '#22c55e' : '#fff' ,
borderRadius : 6 ,
top : 5 ,
width : sliderWidth - 10 ,
height : 40 ,
background : isVerified
? 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)'
: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)' ,
borderRadius : '50%' ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
boxShadow : '0 2px 8px rgba(0,0,0,0.1)' ,
boxShadow : isVerified
? '0 4px 12px rgba(34, 197, 94, 0.4), 0 0 0 4px rgba(34, 197, 94, 0.1)'
: '0 4px 12px rgba(99, 102, 241, 0.35), 0 0 0 4px rgba(255, 255, 255, 0.8)' ,
cursor : isVerified ? 'default' : 'grab' ,
transition : successRef.current ? 'all 0.3s ease' : 'none' ,
userSelect : 'none' ,
zIndex : 2 ,
} }
>
{ isVerified ? (
< svg width = "18 " height = "18 " viewBox = "0 0 24 24" fill = "none" stroke = "#fff" strokeWidth = "2 " strokeLinecap = "round" strokeLinejoin = "round" >
< svg width = "20 " height = "20 " viewBox = "0 0 24 24" fill = "none" stroke = "#fff" strokeWidth = "3 " strokeLinecap = "round" strokeLinejoin = "round" >
< polyline points = "20 6 9 17 4 12" > < / polyline >
< / svg >
) : (
< svg width = "18 " height = "18 " viewBox = "0 0 24 24" fill = "none" stroke = "#6366f1 " strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
< path d = "M 18 8L22 12L18 1 6" > < / path >
< path d = "M2 12h20" > < / path >
< svg width = "20 " height = "20 " viewBox = "0 0 24 24" fill = "none" stroke = "#fff " strokeWidth = "2.5 " strokeLinecap = "round" strokeLinejoin = "round" >
< polyline points = "9 18 15 12 9 6" > < / polyline >
< / svg >
) }
< / div >