fix: 登录页支持 Token 和账号密码两种方式
- 添加 Tab 切换,同时支持 Token 登录和账号密码登录 - Token 登录兼容原有后端 API - 账号密码登录为 Phase 3d 的多管理员功能准备 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0a31f8634e
commit
2a58fbcbae
@ -4,40 +4,68 @@ import { useForm } from "react-hook-form"
|
||||
import { z } from "zod/v4"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Eye, EyeOff } from "lucide-react"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { apiClient } from "@/lib/api-client"
|
||||
import { loginAdmin } from "@/lib/api/admin-api"
|
||||
import { useAuth } from "@/hooks/use-auth"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import type { LoginResponse } from "@/types/api"
|
||||
import type { AdminSession } from "@/types/admin"
|
||||
|
||||
const loginSchema = z.object({
|
||||
// Token 登录表单
|
||||
const tokenLoginSchema = z.object({
|
||||
token: z.string().min(1, "请输入 Admin Token"),
|
||||
})
|
||||
|
||||
type TokenLoginForm = z.infer<typeof tokenLoginSchema>
|
||||
|
||||
// 用户名密码登录表单
|
||||
const passwordLoginSchema = z.object({
|
||||
username: z.string().min(1, "请输入用户名"),
|
||||
password: z.string().min(1, "请输入密码"),
|
||||
})
|
||||
|
||||
type LoginForm = z.infer<typeof loginSchema>
|
||||
type PasswordLoginForm = z.infer<typeof passwordLoginSchema>
|
||||
|
||||
export default function LoginPage() {
|
||||
const navigate = useNavigate()
|
||||
const { login } = useAuth()
|
||||
const [error, setError] = useState("")
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [loginType, setLoginType] = useState<"token" | "password">("token")
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<LoginForm>({
|
||||
resolver: zodResolver(loginSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
// Token 登录表单
|
||||
const tokenForm = useForm<TokenLoginForm>({
|
||||
resolver: zodResolver(tokenLoginSchema),
|
||||
defaultValues: { token: "" },
|
||||
})
|
||||
|
||||
async function onSubmit(data: LoginForm) {
|
||||
// 密码登录表单
|
||||
const passwordForm = useForm<PasswordLoginForm>({
|
||||
resolver: zodResolver(passwordLoginSchema),
|
||||
defaultValues: { username: "", password: "" },
|
||||
})
|
||||
|
||||
// Token 登录
|
||||
async function handleTokenLogin(data: TokenLoginForm) {
|
||||
setError("")
|
||||
try {
|
||||
const response = await apiClient
|
||||
.post("auth/login", { json: { token: data.token } })
|
||||
.json<LoginResponse>()
|
||||
|
||||
login(response.jwt, response.admin)
|
||||
navigate("/")
|
||||
} catch {
|
||||
setError("Token 登录失败,请检查是否正确")
|
||||
}
|
||||
}
|
||||
|
||||
// 密码登录
|
||||
async function handlePasswordLogin(data: PasswordLoginForm) {
|
||||
setError("")
|
||||
try {
|
||||
const response = await loginAdmin(data)
|
||||
@ -62,20 +90,54 @@ export default function LoginPage() {
|
||||
<Card className="w-full max-w-sm">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-xl">多奇管理后台</CardTitle>
|
||||
<CardDescription>使用管理员账号登录</CardDescription>
|
||||
<CardDescription>选择登录方式</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<Tabs value={loginType} onValueChange={(val) => { setLoginType(val as "token" | "password"); setError("") }}>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="token">Token 登录</TabsTrigger>
|
||||
<TabsTrigger value="password">账号登录</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Token 登录 */}
|
||||
<TabsContent value="token" className="mt-4">
|
||||
<form onSubmit={tokenForm.handleSubmit(handleTokenLogin)} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="token">Admin Token</Label>
|
||||
<Input
|
||||
id="token"
|
||||
type="password"
|
||||
placeholder="请输入 Token"
|
||||
{...tokenForm.register("token")}
|
||||
/>
|
||||
{tokenForm.formState.errors.token && (
|
||||
<p className="text-sm text-destructive">{tokenForm.formState.errors.token.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={tokenForm.formState.isSubmitting}
|
||||
>
|
||||
{tokenForm.formState.isSubmitting ? "登录中..." : "登录"}
|
||||
</Button>
|
||||
</form>
|
||||
</TabsContent>
|
||||
|
||||
{/* 密码登录 */}
|
||||
<TabsContent value="password" className="mt-4">
|
||||
<form onSubmit={passwordForm.handleSubmit(handlePasswordLogin)} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">用户名</Label>
|
||||
<Input
|
||||
id="username"
|
||||
placeholder="请输入用户名"
|
||||
autoComplete="username"
|
||||
{...register("username")}
|
||||
{...passwordForm.register("username")}
|
||||
/>
|
||||
{errors.username && (
|
||||
<p className="text-sm text-destructive">{errors.username.message}</p>
|
||||
{passwordForm.formState.errors.username && (
|
||||
<p className="text-sm text-destructive">{passwordForm.formState.errors.username.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -87,7 +149,7 @@ export default function LoginPage() {
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="请输入密码"
|
||||
autoComplete="current-password"
|
||||
{...register("password")}
|
||||
{...passwordForm.register("password")}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
@ -99,19 +161,25 @@ export default function LoginPage() {
|
||||
{showPassword ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
{errors.password && (
|
||||
<p className="text-sm text-destructive">{errors.password.message}</p>
|
||||
{passwordForm.formState.errors.password && (
|
||||
<p className="text-sm text-destructive">{passwordForm.formState.errors.password.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
)}
|
||||
|
||||
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
||||
{isSubmitting ? "登录中..." : "登录"}
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={passwordForm.formState.isSubmitting}
|
||||
>
|
||||
{passwordForm.formState.isSubmitting ? "登录中..." : "登录"}
|
||||
</Button>
|
||||
</form>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{error && (
|
||||
<p className="text-sm text-destructive text-center mt-4">{error}</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user