Files
TurboHR/frontend/src/pages/portal/PortalLogin.tsx
T
2026-07-23 12:34:43 +08:00

130 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Building2 } from 'lucide-react'
import api from '../../lib/api'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
type LoginMode = 'password' | 'code'
export default function PortalLogin() {
const navigate = useNavigate()
const [mode, setMode] = useState<LoginMode>('password')
const [phone, setPhone] = useState('')
const [password, setPassword] = useState('')
const [code, setCode] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const [codeSent, setCodeSent] = useState(false)
const [displayedCode, setDisplayedCode] = useState('')
const handlePasswordLogin = async () => {
setError('')
setLoading(true)
try {
const res = await api.post('/portal/login', { phone, password }) as any
localStorage.setItem('portalToken', res.data.token)
localStorage.setItem('portalEmployee', JSON.stringify(res.data.employee))
navigate('/portal/payslip')
} catch (err: any) {
setError(err.response?.data?.error?.message || '登录失败')
} finally {
setLoading(false)
}
}
const handleSendCode = async () => {
setError('')
try {
const res = await api.post('/portal/send-code', { phone }) as any
setCodeSent(true)
setDisplayedCode(res.data.code)
} catch (err: any) {
setError(err.response?.data?.error?.message || '发送失败')
}
}
const handleCodeLogin = async () => {
setError('')
setLoading(true)
try {
const res = await api.post('/portal/verify-code', { phone, code }) as any
localStorage.setItem('portalToken', res.data.token)
localStorage.setItem('portalEmployee', JSON.stringify(res.data.employee))
navigate('/portal/payslip')
} catch (err: any) {
setError(err.response?.data?.error?.message || '登录失败')
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
<div className="w-full max-w-sm">
<div className="flex items-center justify-center gap-2 mb-8">
<Building2 className="w-8 h-8 text-primary" />
<span className="text-xl font-bold"> </span>
</div>
<div className="card">
<div className="flex border-b mb-4">
<button
onClick={() => { setMode('password'); setError('') }}
className={`flex-1 py-2 text-sm font-medium border-b-2 ${mode === 'password' ? 'border-primary text-primary' : 'border-transparent text-gray-500'}`}
></button>
<button
onClick={() => { setMode('code'); setError('') }}
className={`flex-1 py-2 text-sm font-medium border-b-2 ${mode === 'code' ? 'border-primary text-primary' : 'border-transparent text-gray-500'}`}
></button>
</div>
{error && <div className="mb-4 px-3 py-2 rounded-md bg-red-50 text-red-700 text-sm">{error}</div>}
{mode === 'password' ? (
<div className="space-y-4">
<div>
<Label></Label>
<Input type="tel" placeholder="请输入手机号" value={phone} onChange={(e) => setPhone(e.target.value)} maxLength={11} />
</div>
<div>
<Label></Label>
<Input type="password" placeholder="请输入密码" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
<Button className="w-full" onClick={handlePasswordLogin} disabled={loading || !phone || !password}>
{loading ? '登录中...' : '登录'}
</Button>
</div>
) : (
<div className="space-y-4">
<div>
<Label></Label>
<Input type="tel" placeholder="请输入手机号" value={phone} onChange={(e) => setPhone(e.target.value)} maxLength={11} />
</div>
<div className="flex gap-2">
<div className="flex-1">
<Label></Label>
<Input placeholder="6位验证码" value={code} onChange={(e) => setCode(e.target.value)} maxLength={6} />
</div>
<div className="flex items-end">
<Button variant="secondary" onClick={handleSendCode} disabled={!phone || codeSent}>
{codeSent ? '已发送' : '获取验证码'}
</Button>
</div>
</div>
{codeSent && displayedCode && (
<div className="px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-sm">
{displayedCode}
</div>
)}
<Button className="w-full" onClick={handleCodeLogin} disabled={loading || !phone || !code}>
{loading ? '登录中...' : '登录'}
</Button>
</div>
)}
</div>
</div>
</div>
)
}