「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)

相关推荐
骑鱼过海的猫12326 分钟前
【redis】redis
java·数据库·redis
CT随27 分钟前
Redis 概 述 和 安 装
数据库·redis·缓存
咔咔库奇32 分钟前
【CSS问题】margin塌陷
前端·javascript·css
无敌最俊朗@1 小时前
c#————委托Action使用例子
java·前端·c#
见过夏天1 小时前
CSS 中渐变色的使用
前端·css
不烦下雨c1 小时前
[Mysql基础] 表的操作
数据库·mysql
上官花雨1 小时前
超详细:数据库的基本架构
数据库·架构
764331 小时前
JavaScript ES6 继承 class
前端·javascript
袁代码1 小时前
SwiftUI开发教程系列 - 第十二章:本地化与多语言支持
开发语言·前端·ios·swiftui·swift·ios开发
软件聚导航2 小时前
在uniapp中使用canvas封装组件遇到的坑,数据被后面设备覆盖,导致数据和前面的设备一样
java·前端·uni-app