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

相关推荐
kyriewen11 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
IT_陈寒11 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端
Revolution6112 小时前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试
林焱_RPAAI12 小时前
影刀RPA技术深度:CSS选择器高级实战指南——伪类属性选择器与性能对比完全解析
vue.js·react.js
用户21816970493014 小时前
Flutter(四)Dart语法 空安全 运算符 流程控制
前端
许彰午14 小时前
政务督办的分合模式:主办协办的并发审批
前端·javascript·政务
用户693717500138414 小时前
AI时代,程序员该往哪走?
前端·后端
ttwuai14 小时前
GoFrame 后台日志清空失败:无 WHERE 删除为什么被拦住
前端·golang
易筋紫容14 小时前
创建型模式:对象的诞生艺术
开发语言·前端·javascript
半句唐诗14 小时前
我是如何通过 Access Token 成功发布第一个 npm 包的
前端·npm·node.js