「6」next-shopping:登录注册前端页面实现

作为表单页面,我们需要使用一些好东西来提升开发体验,一个是react-hook-form用于对表单的构建和数据收集,以及zod对表单进行校验,将他们结合在一起就能产生奇妙的化学反应,想要了解更多的话可以查看gayhub地址:github.com/react-hook-...

安装依赖:

shell 复制代码
pnpm i @hookform/resolvers react-hook-form zod

其他

我们先来修复一下app/store/slices/fetchApiSlice.js的问题:将mutation的方法的data入参改名为body

js 复制代码
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

const fetchApi = createApi({
  reducerPath: "fetchApi",
  baseQuery: fetchBaseQuery({ baseUrl: process.env.BASE_URL }),
  endpoints: (builder) => ({
    getData: builder.query({
      query: ({ url, token }) => ({
        url,
        method: "GET",
        headers: { "Content-Type": "application/json", Authorization: token },
      }),
    }),

    postData: builder.mutation({
      query: ({ url, body, token }) => ({
        url,
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: token },
        body,
      }),
    }),

    patchData: builder.mutation({
      query: ({ url, body, token }) => ({
        url,
        method: "PATCH",
        headers: { "Content-Type": "application/json", Authorization: token },
        body,
      }),
    }),

    putData: builder.mutation({
      query: ({ url, body, token }) => ({
        url,
        method: "PUT",
        headers: { "Content-Type": "application/json", Authorization: token },
        body,
      }),
    }),

    deleteData: builder.mutation({
      query: ({ url, token }) => ({
        url,
        method: "DELETE",
        headers: { "Content-Type": "application/json", Authorization: token },
      }),
    }),
  }),
});

export const {
  useGetDataQuery,
  usePostDataMutation,
  usePatchDataMutation,
  usePutDataMutation,
  useDeleteDataMutation,
} = fetchApi;

export default fetchApi;

主要

由于我们的登录注册页面,是不需要HeaderFooter这些的,所以我们需要将这两个页面放在一个新的layout下面

新建app/(main)/(empty-layout)/layout.js,这样就有一个loginregister独享的布局

js 复制代码
export default function Layout({ children }) {
  return <>{children}</>
}

注册页面

新建app/(main)/(empty-layout)/register/page.js

js 复制代码
'use client'

import Image from 'next/image'
import Link from 'next/link'
import * as z from 'zod'
import { toast } from 'react-hot-toast'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { useDispatch } from 'react-redux'

import { usePostDataMutation } from '@/store/slices/fetchApiSlice'
import { userLogin } from '@/store/slices/authSlice'
import { DisplayError, Loading } from '@/components'

const schema = z
  .object({
    name: z
      .string({
        required_error: '用户名必填',
      })
      .min(3, { message: '用户名最少三个字符' }),
    email: z
      .string({
        required_error: '电子邮件地址必填',
      })
      .email('输入的电子邮件地址无效'),
    password: z
      .string({
        required_error: '密码必填',
      })
      .min(6, { message: '密码必须大于6个字符' }),
    confirmPassword: z
      .string({
        required_error: '确认密码必填',
      })
      .min(6, { message: '密码必须大于6个字符' }),
  })
  .refine(data => data.password === data.confirmPassword, {
    message: '确认密码必须与密码一致',
    path: ['confirmPassword'],
  })

export default function RegisterPage() {
  const dispatch = useDispatch()

  const [postData, { data, isSuccess, isError, isLoading, error }] = usePostDataMutation()

  const {
    register,
    handleSubmit,
    formState: { errors: formErrors },
    reset,
  } = useForm({
    resolver: zodResolver(schema),
  })

  useEffect(() => {
    if (isSuccess) {
      toast.success('注册成功')
      dispatch(userLogin(data.data))
      reset()
    }
    if (isError) {
      toast.error(error?.data?.err)
    }
  }, [isSuccess, isError])

  const submitHandler = async ({ name, email, password, confirmPassword }) => {
    if (name && email && password && confirmPassword) {
      postData({ url: '/api/auth/register', body: { name, email, password }, token: '' })
    }
  }

  return (
    <div className=" grid items-center min-h-screen ">
      <div className="container max-w-xl px-12 py-6 space-y-6 lg:border lg:border-gray-100 lg:rounded-lg lg:shadow">
        <div className="relative w-44 h-24 mx-auto">
          <Link passHref href="/">
            <Image src="/images/logo.svg" layout="fill" alt="logo" />
          </Link>
        </div>
        <h2>需要注册</h2>
        <form className="space-y-5" onSubmit={handleSubmit(submitHandler)}>
          <div>
            <input
              type="text"
              className="input"
              name="name"
              placeholder="名称"
              {...register('name')}
            />
            <DisplayError errors={formErrors.name} />
          </div>
          <div>
            <input
              className="input"
              type="text"
              placeholder="电子邮件地址"
              {...register('email')}
            />
            <DisplayError errors={formErrors.email} />
          </div>

          <div>
            <input className="input" type="password" placeholder="密码" {...register('password')} />
            <DisplayError errors={formErrors.password} />
          </div>

          <div>
            <input
              className="input"
              type="password"
              placeholder="重复密码"
              {...register('confirmPassword')}
            />
            <DisplayError errors={formErrors.confirmPassword} />
          </div>

          <button className="btn mx-auto w-60" type="submit" disabled={isLoading}>
            {isLoading ? <Loading /> : '注册'}
          </button>
        </form>

        <div>
          <p className="inline ml-2">已经拥有账户</p>
          <Link href="/login">
            <span className="text-blue-400 text-lg ">登录</span>
          </Link>
        </div>
      </div>
    </div>
  )
}

效果如下:

表单没有居中,且输入框,validateError信息都很丑,我们需要设置统一的样式来覆盖

首先是居中,找到根目录下tailwind.config.ts中修改container:{center: true}

现在居中了,然后我们需要修改input默认的样式,和表单校验错误提示信息

先来看看怎么改,添加自定义样式 - Tailwind CSS 中文网 (nodejs.cn)

可以看到如果是html元素的默认样式我们只需要写到@layer base里面,所以我们找到next初始化自带的global.css,可能在app/global.css

覆盖默认纯html元素样式:

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

 @layer base {

  input {
    box-shadow: 0 0 0px 1000px #fafafa inset;
    -webkit-box-shadow: 0 0 0px 1000px #fafafa inset;
  }
  h2 {
    @apply text-xl;
  }
  p {
    @apply text-base;
  }
}

这次的重点是基于类的样式,所以需要在@layer components下面:

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

 @layer base {

  input {
    box-shadow: 0 0 0px 1000px #fafafa inset;
    -webkit-box-shadow: 0 0 0px 1000px #fafafa inset;
  }
  h2 {
    @apply text-xl;
  }
  p {
    @apply text-base;
  }
}

@layer components {
  .input {
    @apply bg-zinc-50 w-full block
     outline-none py-2 px-3 rounded-md
     text-base lg:text-lg  
     border border-gray-200
   focus:border-blue-600 transition-colors;
  }

  .error-msg {
    @apply mt-1.5 inline-flex gap-x-1 justify-center items-center text-sm text-red-600;
  }

  .btn {
    @apply text-white bg-red-500 py-3 px-4 rounded-3xl block outline-none;
  }
}

效果如下:

试一试注册:我们注册成功了

并且我们在前端页面注册成功后dispatch了用户登录,可以看到浏览器cookies里面已经存了用户信息和token

登录页面

新建app/(main)/(empty-layout)/login/page.js

js 复制代码
'use client'

import { useEffect } from 'react'
import Link from 'next/link'
import Image from 'next/image'
import * as z from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { toast } from 'react-hot-toast'
import { useDispatch } from 'react-redux'

import { usePostDataMutation } from '@/store/slices/fetchApiSlice'
import { DisplayError, Loading } from '@/components'
import { userLogin } from '@/store/slices/authSlice'

const schema = z.object({
  email: z
    .string({
      required_error: '邮件地址必填',
    })
    .email('输入的电子邮件地址无效'),
  password: z
    .string({
      required_error: '密码必填',
    })
    .min(6, { message: '密码最少6个字符' }),
})

export default function LoginPage() {
  const dispatch = useDispatch()

  const [postData, { data, isSuccess, isError, isLoading, error }] = usePostDataMutation()

  const {
    handleSubmit,
    register,
    formState: { errors: formErrors },
    reset,
  } = useForm({
    resolver: zodResolver(schema),
  })

  useEffect(() => {
    if (isSuccess) {
      toast.success(data.msg)
      dispatch(userLogin(data.data))
      reset()
    }
    if (isError) {
      toast.error(error?.data.err)
    }
  }, [isSuccess, isError])

  const submitHandler = async ({ email, password }) => {
    if (email && password) {
      await postData({
        url: '/api/auth/login',
        body: { email, password },
        token: '',
      })
    }
  }

  return (
    <div className="grid items-center min-h-screen ">
      <div className="container max-w-xl px-12 py-6 space-y-6 lg:border lg:border-gray-100 lg:rounded-lg lg:shadow">
        <div className="relative w-44 h-24 mx-auto">
          <Link passHref href="/">
            <Image src="/images/logo.svg" layout="fill" alt="logo" />
          </Link>
        </div>
        <h2>登录</h2>
        <form className="space-y-5" onSubmit={handleSubmit(submitHandler)}>
          <div>
            <input
              className="input"
              type="text"
              placeholder="电子邮件地址"
              {...register('email')}
            />
            <DisplayError errors={formErrors.email} />
          </div>

          <div>
            <input className="input" type="password" placeholder="密码" {...register('password')} />
            <DisplayError errors={formErrors.password} />
          </div>

          <button className="btn mx-auto w-60" type="submit" disabled={isLoading}>
            {isLoading ? <Loading /> : '登录'}
          </button>
        </form>

        <div>
          <p className="inline ml-2">你还没有注册</p>
          <Link href="/register">
            <span className="text-blue-400 text-lg ">注册</span>
          </Link>
        </div>
      </div>
    </div>
  )
}

我们尝试一下登录:

非常棒,今天就到这儿了,摸鱼时间没有啦,需要忙工作😂,下次再见

代码地址:feat: 实现前端页面登录注册 · liyunfu1998/next-shopping@99721b2 (github.com)

相关推荐
阿珊和她的猫29 分钟前
CSS3新特性概述
前端·css·css3
前端小端长1 小时前
qiankun 微前端应用入门教程:从搭建到部署
前端
yinuo4 小时前
前端跨页面通讯终极指南⑥:SharedWorker 用法全解析
前端
廋到被风吹走8 小时前
【数据库】【MySQL】InnoDB外键解析:约束机制、性能影响与最佳实践
android·数据库·mysql
掘根8 小时前
【消息队列】交换机数据管理实现
网络·数据库
Logic1018 小时前
《Mysql数据库应用》 第2版 郭文明 实验6 数据库系统维护核心操作与思路解析
数据库·sql·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
PineappleCoder8 小时前
还在重复下载资源?HTTP 缓存让二次访问 “零请求”,用户体验翻倍
前端·性能优化
拉不动的猪8 小时前
webpack编译中为什么不建议load替换ast中节点删除consolg.log
前端·javascript·webpack
李姆斯8 小时前
Agent时代下,ToB前端的UI和交互会往哪走?
前端·agent·交互设计
AI Echoes9 小时前
构建一个LangChain RAG应用
数据库·python·langchain·prompt·agent