作为表单页面,我们需要使用一些好东西来提升开发体验,一个是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;
主要
由于我们的登录注册页面,是不需要Header
和Footer
这些的,所以我们需要将这两个页面放在一个新的layout
下面
新建app/(main)/(empty-layout)/layout.js
,这样就有一个login
和register
独享的布局
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)