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 { z } from "zod/v4"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { Eye, EyeOff } from "lucide-react"
|
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 { loginAdmin } from "@/lib/api/admin-api"
|
||||||
import { useAuth } from "@/hooks/use-auth"
|
import { useAuth } from "@/hooks/use-auth"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import type { LoginResponse } from "@/types/api"
|
||||||
import type { AdminSession } from "@/types/admin"
|
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, "请输入用户名"),
|
username: z.string().min(1, "请输入用户名"),
|
||||||
password: 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() {
|
export default function LoginPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { login } = useAuth()
|
const { login } = useAuth()
|
||||||
const [error, setError] = useState("")
|
const [error, setError] = useState("")
|
||||||
const [showPassword, setShowPassword] = useState(false)
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
const [loginType, setLoginType] = useState<"token" | "password">("token")
|
||||||
|
|
||||||
const {
|
// Token 登录表单
|
||||||
register,
|
const tokenForm = useForm<TokenLoginForm>({
|
||||||
handleSubmit,
|
resolver: zodResolver(tokenLoginSchema),
|
||||||
formState: { errors, isSubmitting },
|
defaultValues: { token: "" },
|
||||||
} = useForm<LoginForm>({
|
|
||||||
resolver: zodResolver(loginSchema),
|
|
||||||
defaultValues: {
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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("")
|
setError("")
|
||||||
try {
|
try {
|
||||||
const response = await loginAdmin(data)
|
const response = await loginAdmin(data)
|
||||||
@ -62,20 +90,54 @@ export default function LoginPage() {
|
|||||||
<Card className="w-full max-w-sm">
|
<Card className="w-full max-w-sm">
|
||||||
<CardHeader className="text-center">
|
<CardHeader className="text-center">
|
||||||
<CardTitle className="text-xl">多奇管理后台</CardTitle>
|
<CardTitle className="text-xl">多奇管理后台</CardTitle>
|
||||||
<CardDescription>使用管理员账号登录</CardDescription>
|
<CardDescription>选择登录方式</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="username">用户名</Label>
|
<Label htmlFor="username">用户名</Label>
|
||||||
<Input
|
<Input
|
||||||
id="username"
|
id="username"
|
||||||
placeholder="请输入用户名"
|
placeholder="请输入用户名"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
{...register("username")}
|
{...passwordForm.register("username")}
|
||||||
/>
|
/>
|
||||||
{errors.username && (
|
{passwordForm.formState.errors.username && (
|
||||||
<p className="text-sm text-destructive">{errors.username.message}</p>
|
<p className="text-sm text-destructive">{passwordForm.formState.errors.username.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -87,7 +149,7 @@ export default function LoginPage() {
|
|||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
{...register("password")}
|
{...passwordForm.register("password")}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@ -99,19 +161,25 @@ export default function LoginPage() {
|
|||||||
{showPassword ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
{showPassword ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{errors.password && (
|
{passwordForm.formState.errors.password && (
|
||||||
<p className="text-sm text-destructive">{errors.password.message}</p>
|
<p className="text-sm text-destructive">{passwordForm.formState.errors.password.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
<Button
|
||||||
<p className="text-sm text-destructive">{error}</p>
|
type="submit"
|
||||||
)}
|
className="w-full"
|
||||||
|
disabled={passwordForm.formState.isSubmitting}
|
||||||
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
>
|
||||||
{isSubmitting ? "登录中..." : "登录"}
|
{passwordForm.formState.isSubmitting ? "登录中..." : "登录"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-destructive text-center mt-4">{error}</p>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user