组件
首先我们需要在Icons组件中引入更多的icon图标,修改components/share/Icons.js
js
import { BsEyeSlash, BsEye, BsCart2, BsShieldExclamation, BsQuestionSquare } from 'react-icons/bs'
import { FiLogIn, FiLogOut } from 'react-icons/fi'
import { VscThreeBars } from 'react-icons/vsc'
import { HiOutlineUser } from 'react-icons/hi'
import { IoSearch } from 'react-icons/io5'
import { RiArrowDownSFill, RiArrowDropLeftLine } from 'react-icons/ri'
const Icons = {
Eye: BsEye,
EyeSlash: BsEyeSlash,
Cart: BsCart2,
Login: FiLogIn,
Logout: FiLogOut,
Exclamation: BsShieldExclamation,
Bars: VscThreeBars,
User: HiOutlineUser,
Question: BsQuestionSquare,
Search: IoSearch,
ArrowDown: RiArrowDownSFill,
ArrowLeft: RiArrowDropLeftLine,
}
export default Icons
新建components/Cart.js
js
import Link from 'next/link'
import { Icons } from '@/components'
export default function Cart() {
return (
<Link href="/cart">
<span>
<Icons.Cart className="icon" />
</span>
</Link>
)
}
新建components/Search.js
js
import { Icons } from '@/components'
export default function Search() {
return (
<form className="flex-grow flex flex-row-reverse max-w-3xl rounded-md bg-zinc-200/80">
<input
type="text"
placeholder="搜索"
className="text-right outline-none bg-transparent p-1 flex-grow"
/>
<button className="p-2">
<Icons.Search className="icon" />
</button>
</form>
)
}
新建components/User.js
js
import Link from 'next/link'
import Image from 'next/image'
import { useState } from 'react'
import { Icons } from './index'
import { userLogout } from '../store/slices/authSlice'
export default function User({ user, dispatch }) {
const [isOpen, setIsOpen] = useState(false)
if (!user) {
return (
<div className="flex items-center gap-x-2 lg:border lg:border-gray-300 lg:rounded-md lg:py-2 lg:px-3 text-sm">
<Link href="/login">
<span className="flex items-center gap-x-1">
<Icons.Login className="icon" />
输入
</span>
</Link>
<span className="hidden lg:block lg:border lg:border-gray-300 lg:h-6"></span>
<Link href="/register">
<span className="hidden lg:block px-2">注册</span>
</Link>
</div>
)
}
return (
<>
<div className="lg:hidden">
<Link href="/profile">
<span>
<Icons.User className="icon" />
</span>
</Link>
</div>
<div
className={`hidden lg:relative lg:flex lg:rounded lg:p-1.5 lg:transition ${isOpen && 'bg-red-100'}`}
onClick={() => setIsOpen(!isOpen)}
>
<Icons.User className="icon" />
<Icons.ArrowDown className="icon" />
<div
className={`bg-white shadow-md px-3 py-2 absolute top-full left-0 w-56 border border-gray-100 space-y-3 ${
isOpen ? 'block' : 'hidden'
}`}
>
<Link href="/profile" passHref>
<span className="flex items-center py-3 border-b border-gray-200 gap-x-2">
<div className="realative w-6 h-6">
<Image src={'/avatar.png'} alt="user" layout="fill" />
</div>
<span className="min-w-max">{user.name}</span>
<Icons.ArrowLeft className="icon mr-auto" />
</span>
</Link>
<button className="flex items-center py-3 gap-x-2" onClick={() => dispatch(userLogout())}>
<Icons.Logout className="icon" />
注销用户账号
</button>
</div>
</div>
</>
)
}
注意:Image里面的图片需要放在public
目录下面
新建components/Navbar.js
js
import Image from 'next/image'
import Link from 'next/link'
import { Icons, User, Cart, Search } from './index'
import { useDispatch, useSelector } from 'react-redux'
export default function Navbar() {
const dispatch = useDispatch()
const { user } = useSelector(state => state.auth)
return (
<header className="px-4 lg:shadow">
<div className="container max-w-[1550px] lg:flex lg:py-2">
<div className="inline-flex justify-between w-full items-center border-b lg:border-b-0 lg:max-w-min lg:ml-8">
<button className="p-1 lg:hidden">
<Icons.Bars className="icon" />
</button>
<div className="relative w-24 h-14">
<Link href="/" passHref>
<span>
<Image src="/images/logo.svg" layout="fill" />
</span>
</Link>
</div>
<Icons.Question className="icon lg:hidden" />
</div>
<div className="inline-flex gap-x-10 justify-between py-2 w-full items-center border-b lg:border-b-0">
<Search />
<div className="inline-flex items-center gap-x-4">
<User user={user} dispatch={dispatch} />
<span className="hidden lg:block lg:border lg:border-gray-300 lg:h-6"></span>
<Cart />
</div>
</div>
</div>
</header>
)
}
将组件通过components/index.js
导出
js
export { default as Icons } from './share/Icons'
export { default as Loading } from './share/Loading'
export { default as DisplayError } from './share/DisplayError'
export { default as Navbar } from './Navbar'
export { default as User } from './User'
export { default as Cart } from './Cart'
export { default as Search } from './Search'
页面
目前我们已经有了登录和注册页面,这两个都是不需要Footer
和Nav
的,所以我们还需要单独建一个Layout
用于需要这两个组件的通用layout
新建app/(main)/(normal-layout)/layout.js
js
'use client'
import { Navbar } from '@/components'
export default function Layout({ children }) {
return (
<>
<Navbar />
{children}
<footer>footer</footer>
</>
)
}
新建app/(main)/(normal-layout)/page.js
js
export default function Page() {
return <>这里是普通页面</>
}
我们打开页面http://localhost:3000/
发现好像不太对,原来是app/(main)
下面还有一个page
,所以我们删除它
好了效果如下:
样式
修改global.css
添加上根据类匹配的icon
和b
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;
}
button {
@apply cursor-pointer;
}
a {
@apply cursor-pointer;
}
button {
@apply cursor-pointer;
}
a {
@apply cursor-pointer;
}
button {
@apply cursor-pointer;
}
a {
@apply cursor-pointer;
}
button {
@apply cursor-pointer;
}
a {
@apply cursor-pointer;
}
}
@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;
}
.icon {
@apply w-6 h-6 text-gray-700;
}
.b {
@apply border border-red-600;
}
}
测试登录后效果如下:
最后我们补充一下退出登录的逻辑,删掉state.user
和state.token
修改store/slices/authSlice.js
js
import { createSlice } from '@reduxjs/toolkit'
import Cookies from 'js-cookie'
const initialState = {
userInfo: Cookies.get('userInfo') ? JSON.parse(Cookies.get('userInfo')) : {},
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
userLogin: (state, action) => {
const { user, access_token: token } = action.payload
state.token = token
state.user = user
Cookies.set('userInfo', JSON.stringify({ token, user }))
},
userLogout: (state, action) => {
// state = {} 注意这里: 直接对state进行改变,是不行的,相当于state丢失了
Cookies.remove('userInfo')
state.token = null
state.user = null
},
},
})
export const { userLogin, userLogout } = authSlice.actions
export default authSlice.reducer