「8」next-shopping:完善Nav、购物车、用户、搜索组件

组件

首先我们需要在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'

页面

目前我们已经有了登录和注册页面,这两个都是不需要FooterNav的,所以我们还需要单独建一个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添加上根据类匹配的iconb

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.userstate.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

代码地址:github.com/liyunfu1998...

相关推荐
小白学习日记40 分钟前
【复习】HTML常用标签<table>
前端·html
john_hjy43 分钟前
11. 异步编程
运维·服务器·javascript
风清扬_jd1 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java1 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele1 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
It'sMyGo2 小时前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式
懒羊羊大王呀2 小时前
CSS——属性值计算
前端·css
xgq2 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
李是啥也不会2 小时前
数组的概念
javascript
用户3157476081352 小时前
前端之路-了解原型和原型链
前端