React通用登录/注销功能实现方案(基于shadcn/ui)
- 一、功能需求分析
- 二、通用功能封装
- 1. 通用登录表单组件
- 2. 认证Hook封装
- 三、功能使用示例
- 1. 登录页面实现
- 2. 用户菜单实现
- 四、路由保护实现
- 五、方案优势
一、功能需求分析
需要实现以下核心功能:
- 登录表单组件
- 登录状态管理
- 用户注销功能
- 路由权限控制
二、通用功能封装
1. 通用登录表单组件
// 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?: stringtitle: stringdescription?: stringerror?: stringfields: FormField[]submitText?: stringonSubmit: (data: Record<string, string>) => voidchildren?: ReactNode
}export type FormField = {name: stringlabel: stringtype?: stringplaceholder?: stringrequired?: 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><Inputid={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封装
// 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. 登录页面实现
// 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"><AuthFormtitle="Login to System"description="Enter your credentials to continue"fields={loginFields}onSubmit={login}error={error}submitText="Sign In"/></div></div>)
}
2. 用户菜单实现
// 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>)
}
四、路由保护实现
// 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组件分离
- 易于扩展:支持添加注册/找回密码等衍生功能