React通用登录/注销功能实现方案(基于shadcn/ui)
一、功能需求分析
需要实现以下核心功能:
- 登录表单组件
- 登录状态管理
- 用户注销功能
- 路由权限控制
二、通用功能封装
1. 通用登录表单组件
tsx
// lib/components/auth-form.tsx
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { FormEvent, ReactNode } from "react"
interface AuthFormProps {
className?: string
title: string
description?: string
error?: string
fields: FormField[]
submitText?: string
onSubmit: (data: Record<string, string>) => void
children?: ReactNode
}
export type FormField = {
name: string
label: string
type?: string
placeholder?: string
required?: boolean
}
export function AuthForm({
className,
title,
description,
error,
fields,
submitText = "Submit",
onSubmit,
children
}: AuthFormProps) {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const data = Object.fromEntries(formData.entries())
onSubmit(Object.fromEntries(
Object.entries(data).map(([key, value]) => [key, value.toString()])
))
}
return (
<div className={cn("flex flex-col gap-6", className)}>
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
{description && <CardDescription>{description</CardDescription>}
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit}>
<div className="flex flex-col gap-6">
{error && (
<div className="text-sm font-medium text-destructive">
{error}
</div>
)}
{fields.map((field) => (
<div key={field.name} className="grid gap-3">
<Label htmlFor={field.name}>{field.label}</Label>
<Input
id={field.name}
name={field.name}
type={field.type || "text"}
required={field.required !== false}
placeholder={field.placeholder}
/>
</div>
))}
<Button type="submit" className="w-full">
{submitText}
</Button>
</div>
</form>
{children}
</CardContent>
</Card>
</div>
)
}
2. 认证Hook封装
tsx
// lib/hooks/use-auth.ts
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
export const useAuth = () => {
const [error, setError] = useState('')
const navigate = useNavigate()
const login = async (credentials: Record<string, string>) => {
try {
// 示例验证逻辑,实际替换为API调用
if (credentials.username === 'admin' && credentials.password === '123456') {
localStorage.setItem('isAuthenticated', 'true')
navigate('/')
} else {
setError('Invalid credentials')
}
} catch (err) {
setError('Login failed')
}
}
const logout = () => {
localStorage.removeItem('isAuthenticated')
navigate('/login')
}
return { login, logout, error }
}
三、功能使用示例
1. 登录页面实现
tsx
// app/login/page.tsx
import { AuthForm } from "@/lib/components/auth-form"
import { useAuth } from "@/lib/hooks/use-auth"
export default function LoginPage() {
const { login, error } = useAuth()
const loginFields = [
{ name: "username", label: "Username", required: true },
{ name: "password", label: "Password", type: "password", required: true }
]
return (
<div className="flex h-screen items-center justify-center bg-gray-100 p-4">
<div className="w-full max-w-md">
<AuthForm
title="Login to System"
description="Enter your credentials to continue"
fields={loginFields}
onSubmit={login}
error={error}
submitText="Sign In"
/>
</div>
</div>
)
}
2. 用户菜单实现
tsx
// components/nav-user.tsx
import { useAuth } from "@/lib/hooks/use-auth"
export function NavUser() {
const { logout } = useAuth()
return (
<DropdownMenu>
{/* 其他菜单项 */}
<DropdownMenuItem onClick={logout}>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenu>
)
}
四、路由保护实现
tsx
// router.ts
import { Navigate } from 'react-router-dom'
const PrivateRoute = ({ children }: { children: JSX.Element }) => {
const isAuthenticated = localStorage.getItem('isAuthenticated')
return isAuthenticated ? children : <Navigate to="/login" replace />
}
五、方案优势
- 高度可配置:表单字段、验证逻辑均可自定义
- 类型安全:完善的TypeScript类型定义
- UI解耦:业务逻辑与UI组件分离
- 易于扩展:支持添加注册/找回密码等衍生功能