React通用登录/注销功能实现方案(基于shadcn/ui)

React通用登录/注销功能实现方案(基于shadcn/ui)

一、功能需求分析

需要实现以下核心功能:

  1. 登录表单组件
  2. 登录状态管理
  3. 用户注销功能
  4. 路由权限控制

二、通用功能封装

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 />
}

五、方案优势

  1. 高度可配置:表单字段、验证逻辑均可自定义
  2. 类型安全:完善的TypeScript类型定义
  3. UI解耦:业务逻辑与UI组件分离
  4. 易于扩展:支持添加注册/找回密码等衍生功能
相关推荐
用泥种荷花15 小时前
【前端学习AI】FewShotPromptTemplate
前端
小魔女千千鱼15 小时前
在 Vue 中,this 的行为在箭头函数和普通函数中是不同的
前端·javascript·vue.js
霍理迪15 小时前
CSS盒模型布局规则
前端·javascript·css
千寻girling16 小时前
面试官: “ 说一下 JS 中什么是事件循环 ? ”
前端·javascript
程序员龙语16 小时前
CSS 高级选择器应用
前端·css
Cassie燁16 小时前
el-table源码解读2-2——createStore()初始化方法
前端·javascript·vue.js
程序员修心16 小时前
CSS文本样式全解析:11个核心属性详解
前端·css
旧梦吟16 小时前
脚本网站 开源项目
前端·web安全·网络安全·css3·html5
北极糊的狐16 小时前
按钮绑定事件达成跳转效果并将树结构id带入子页面形成参数完成查询功能并将返回的数据渲染到页面上2022.5.29
前端·javascript·vue.js
幽络源小助理16 小时前
幽络源二次元分享地址发布页源码(HTML) – 源码网免费分享
前端·html