chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、 文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Lock, User, Loader2, Eye, EyeOff } from 'lucide-react'
|
||||
import { Lock, User, Loader2, Eye, EyeOff, ShieldCheck } from 'lucide-react'
|
||||
import { api } from '@/lib/api-client'
|
||||
import { login } from '@/lib/auth'
|
||||
import { ApiRequestError } from '@/lib/api-client'
|
||||
@@ -11,7 +11,9 @@ export default function LoginPage() {
|
||||
const router = useRouter()
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [totpCode, setTotpCode] = useState('')
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [needTotp, setNeedTotp] = useState(false)
|
||||
const [remember, setRemember] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
@@ -31,12 +33,23 @@ export default function LoginPage() {
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await api.auth.login({ username: username.trim(), password })
|
||||
const res = await api.auth.login({
|
||||
username: username.trim(),
|
||||
password,
|
||||
totp_code: totpCode.trim() || undefined,
|
||||
})
|
||||
login(res.token, res.account)
|
||||
router.replace('/')
|
||||
} catch (err) {
|
||||
if (err instanceof ApiRequestError) {
|
||||
setError(err.body.message || '登录失败,请检查用户名和密码')
|
||||
const msg = err.body.message || ''
|
||||
// 后端返回 "需要 TOTP" 时显示 TOTP 输入框
|
||||
if (msg.includes('TOTP') || msg.includes('totp') || msg.includes('2FA') || msg.includes('验证码') || err.status === 403) {
|
||||
setNeedTotp(true)
|
||||
setError(msg || '请输入两步验证码')
|
||||
} else {
|
||||
setError(msg || '登录失败,请检查用户名和密码')
|
||||
}
|
||||
} else {
|
||||
setError('网络错误,请稍后重试')
|
||||
}
|
||||
@@ -152,6 +165,35 @@ export default function LoginPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TOTP 验证码 */}
|
||||
{needTotp && (
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="totp"
|
||||
className="text-sm font-medium text-foreground"
|
||||
>
|
||||
两步验证码
|
||||
</label>
|
||||
<div className="relative">
|
||||
<ShieldCheck className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
id="totp"
|
||||
type="text"
|
||||
placeholder="请输入 6 位验证码"
|
||||
value={totpCode}
|
||||
onChange={(e) => setTotpCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
||||
maxLength={6}
|
||||
className="flex h-10 w-full rounded-md border border-input bg-transparent pl-10 pr-3 py-2 text-sm shadow-sm transition-colors duration-200 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring tracking-widest"
|
||||
autoComplete="one-time-code"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
请使用身份验证器 App(如 Google Authenticator)扫描二维码后生成的验证码
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 记住我 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user