【欢迎初学者🔰】使用Next.js构建购物网站🛒

这次的作品

 

GitHub的仓库

 

实施功能

    商品一覧を表示(Top画面)
Image from Gyazo
    ログイン・ログアウト機能(Contact画面)
Image from Gyazo
    カートに追加(Cart画面)
Image from Gyazo
    小計&合計金額の表示(Cart画面)
Image from Gyazo

借鉴的视频

 

请留意事项。

开发环境

操作系统: windows10
编辑器: VScode
终端: Bash
node.js版本: v18.17.0

这次我们以初学者为主要对象,所以我们使用JavaScript而不是TypeScript进行编写。
(如果你熟悉TypeScript,请务必尝试使用TypeScript进行编写!)
为了将重点放在Next.js的逻辑部分上,我们省略了对设计部分的说明。
CSS的代码如下所示:
CSS的代码sample.css
h1 {
color:red;
}

步骤1:在Vercel上配置部署环境。

image.png

在终端中运行 npx create-next-app

設定已按照以下方式進行。
這次我們將創建src文件夾。

√ What is your project named? ... shopping-cart
√ Would you like to use TypeScript? ... No
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... No
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) » Yes
√ Would you like to customize the default import alias (@/*)? » No

通过Vercel登录

image.png

创建一个新项目

image.png

将Github与其他服务进行整合

image.png

建筑会自动开始。

image.png

步骤2:设置应用程序路由配置

在app文件夹的根目录下创建每个页面的文件夹。
本次创建cart页面和contact页面。
在每个页面的根目录下创建page.jsx文件。

└─app
    │  favicon.ico
    │  globals.css
    │  layout.js
    │  page.js
    │
    ├─cart
    │      page.jsx
    │
    └─contact
            page.jsx
import React from "react";

const Cart = () => {
  return <div>Cart</div>;
};

export default Cart;
import React from "react";

const Contact = () => {
  return <div>Contact</div>;
};

export default Contact;

在中文中以原生方式改述:
运行 npm run dev 来启动服务器,
访问 http://localhost:3000/cart,如果显示 “Cart” 就表示正常!
(为了确保,访问 http://localhost:3000/contact,检查是否显示 “Contact”)

导航设置

创建一个components文件夹,并创建navbar.jsx和navbar.css。

└─app
    ├─cart
    ├─components
    │      navbar.css
    │      navbar.jsx  
    └─contact
import Link from "next/link";
import React from "react";
import "./navbar.css";

function Navbar() {
  return (
    <div className="navbar">
      <div className="links">
        <Link href="/">Shop</Link>
        <Link href="/contact">Contact</Link>
        <Link href="/cart">Cart</Link>
      </div>
    </div>
  );
}

export default Navbar;

※CSS的代码被折叠了。

导航栏.css组件/导航栏.css
.导航栏 {
宽度: 100%;
高度: 80像素;
背景颜色: rgb(19, 19, 19);
显示: 弹性;
主对齐方式: 弹性结束;
纵向对齐方式: 居中;
}

.链接 {
右边距: 50像素;
显示: 弹性;
纵向对齐方式: 居中;
}

.链接 a {
文字装饰: 无;
颜色: 白色;
左边距: 20像素;
字体大小: 25像素;
}

在src/app/layout.js中添加navbar组件

import { Inter } from 'next/font/google'
import './globals.css'
+ import Navbar from './components/navbar'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
+        <Navbar />
        {children}
      </body>
    </html>
  )
}

第三步-创建主页界面

这次,我们将使用以下免费的API来获取商品数据。

 

请不要忘记在开头加上”use client”!如果要使用useEffect和useState等Hooks,需要进行CSR(客户端渲染)。

"use client";
import React, { useEffect, useState } from "react";

function Shop() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch("https://fakestoreapi.com/products")
      .then((res) => res.json())
      .then((json) => {
        console.log(json);
        setItems(json);
      });
  }, []);

  return (
    <div className="shop">
      <div className="shopTitle">
        <h1>E-commerce Shop</h1>
      </div>

      <div className="products">
        {items.map((item) => (
          <div key={item.id}>
            <p>{item.id}</p>
            <p>{item.title}</p>
            <p>{item.price}</p>
            <p>{item.category}</p>
            <p>{item.description}</p>
            <img src={item.image} />
          </div>
        ))}
      </div>
    </div>
  );
}

export default Shop;

+ import Shop from './components/shop'

export default function Home() {
  return (
    <main>
+      <Shop />
    </main>
  )
}

对Step4 Home画面进行风格设计。

在主页上应用CSS并调整布局。
在shop.jsx中添加className,并在CSS中进行定义。

"use client";
import React, { useEffect, useState } from "react";
+ import "./shop.css";

function Shop() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch("https://fakestoreapi.com/products")
      .then((res) => res.json())
      .then((json) => {
        console.log(json);
        setItems(json);
      });
  }, []);

  return (
    <div className="shop">
      <div className="shopTitle">
        <h1>E-commerce Shop</h1>
      </div>

      <div className="products">
        {items.map((item) => (
+          <div key={item.id} className="product">
+            <div className="content">
+              <p className="title">
+                {item.id}. {item.title}
+              </p>
+              <img src={item.image} alt={item.title} className="image" />
+              <p className="price">${item.price}</p>
+              <p className="description">{item.description}</p>
+              <p className="category">{item.category}</p>
+            </div>
+            <button className="addToCartBtn">ADD</button>
+          </div>
        ))}
      </div>
    </div>
  );
}

export default Shop;

店铺的CSS代码src/app/components/shop.css
.shopTitle {
margin-top: 100px;
text-align: center;
font-size: 40px;
}

.products {
width: 100%;
height: auto;
margin: auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
place-items: start;
}

.product {
border-radius: 15px;
width: 300px;
height: fit-content;
margin: 50px auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.content {
display: flex;
flex-direction: column;
justify-content: flex-start;
margin-bottom: 10px;
}

.image {
max-width: 100%;
max-height: 300px;
object-fit: contain;
transition: 0.3s;
margin: 50px auto;
}

.title {
font-size: 1.2em;
font-weight: bold;
}

.content {
text-align: center;
}

.description {
font-style: italic;
font-size: 0.8em;
margin: 15px auto;
text-align: left;
}

.price {
font-weight: bold;
font-size: 2em;
}

.category {
background-color: darkblue;
color: white;
border-radius: 100px;
width: fit-content;
padding: 2px 10px;
}

.image:hover {
transform: scale(1.2);
position: relative;
}

.product:hover {
transition: 0.3s ease-in;
}

.title:hover,
.image:hover {
cursor: pointer;
}

.addToCartBtn {
background-color: transparent;
border: 2px solid rgb(19, 19, 19);
min-width: 100px;
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
border-radius: 15px;
transition: 0.3s;
}

.addToCartBtn:hover {
background-color: rgb(19, 19, 19);
color: white;
cursor: pointer;
}

步骤5:创建联系页面

我们将创建一个联系页面。
根据登录状态,使用 useState 来切换屏幕显示。

"use client";

import React, { useState } from "react";
import "./contact.css";

const Contact = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [isLogin, setIsLogin] = useState(false);

  const handleUsername = (e) => {
    setUsername(e.target.value);
  };

  const handlePassword = (e) => {
    setPassword(e.target.value);
  };

  const handleLogin = () => {
    if (username === "" || password === "") return;
    setIsLogin(true);
  };

  const handleLogout = () => {
    setUsername("");
    setPassword("");
    setIsLogin(false);
  };

  return (
    <div className="wrapper">
      <div className="content">
        {isLogin ? (
          <button className="button" onClick={handleLogout}>
            logout
          </button>
        ) : (
          <>
            <div className="input">
              ユーザー名:
              <input
                type="text"
                name="username"
                id="username"
                className="username"
                onChange={handleUsername}
              />
            </div>
            <div className="input">
              パスワード:
              <input
                type="password"
                name="password"
                id="password"
                className="password"
                onChange={handlePassword}
              />
            </div>
            <button className="button" onClick={handleLogin}>
              login
            </button>
          </>
        )}
      </div>
    </div>
  );
};

export default Contact;

以下是原生中文的重新表述(仅供参考):contact.css

src/app/contact/contact.css
.wrapper {
显示:flex;
对齐项目:居中;
高度:90vh;
}

.content {
宽度:适应内容;
边距:自动;
文本对齐:居中;
}

.input {
边距:10px 自动;
}

.username,
.password {
显示:内联块;
左边距:5px;
填充:2.5像素 5像素;
}

.button {
字重:粗体;
填充:5像素;
}

第六步 创建购物车页面

我們將創建購物車頁面。
首先,因為我們想要調整版面,所以暫時放入了圖像等資料。

import React from "react";
import "./cart.css";

const Cart = () => {
  return (
    <div className="cart">
      <div>
        <h1>カートの商品</h1>
      </div>
      <div className="cart">
        <div className="cartItem">
          <img src="https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg" />
          <div className="description">
            <p>
              <b>name</b>
            </p>
            <p>price</p>
            <div className="countHandler">
              <button> - </button>
              <input value={"1"} />
              <button> + </button>
            </div>
          </div>
        </div>
      </div>

      <div className="checkout">
        <p>小計: xxxx</p>
        <button>買い物を続ける</button>
        <button>チェックアウト</button>
      </div>
    </div>
  );
};

export default Cart;

cart.csssrc/app/cart/cart.css
.cart {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 30px;
}

.cartItem {
width: 700px;
height: 250px;
display: flex;
align-items: center;
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
border-radius: 25px;
margin: 30px;
padding: 20px;
}

.cartItem img {
width: 200px;
max-height: 90%;
object-fit: contain;
}

.cartItem .description {
width: 100%;
font-size: 30px;
padding-left: 20px;
}

.cartItemTitle {
font-weight: bold;
font-size: 18px;
margin-bottom: 10px;
}

countHandler input {
width: 40px;
text-align: center;
font-weight: bolder;
margin: 0 5px;
}

countHandler button {
padding: 0 0.5em;
}

subtotal {
margin-top: 20px;
font-size: 24px;
}

total {
font-size: 36px;
}

checkout button {
width: 150px;
height: 50px;
background-color: rgb(19, 19, 19);
color: white;
border: none;
border-radius: 8px;
margin: 10px;
cursor: pointer;
}

第七步【重要!】使用useContext进行状态管理。

在源文件夹的直接下方创建一个上下文文件夹,
并创建一个名为shop-context.js的文件。

请参考以下文章以获得有关useContext的详细使用方法!

React Context的使用方法(useContext)
【React Hooks】useContext是什么?概念和使用方法的详细解释!

 └─src
     ├─app
     │  ├─cart
     │  ├─components
+    │  └─contact
     └─context
"use client"
import { createContext, useState } from "react";

// グローバルなpropsを定義し、初期値を渡す
export const ShopContext = createContext(null);

// カート内の商品の初期状態を生成
const getDefaultCart = () => {
  const productsLength = 20;
  let cart = {};
  for (let i = 1; i < productsLength + 1; i++) {
    cart[i] = 0;
  }
  return cart;
}

export const ShopContextProvider = (props) => {
  const [cartItems, setCartItems] = useState(getDefaultCart());

  const getTotalCartAmount = () => {
    let totalAmount = 0;
    for (const item in cartItems) {
      if (cartItems[item] > 0) {
        let itemInfo = items.find((product) => product.id === Number(item));
        totalAmount += cartItems[item] * itemInfo.price;
      }
    }
    return totalAmount;
  };

  const addToCart = (itemId) => {
    setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] + 1 }));
  };

  const removeFromCart = (itemId) => {
    setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] - 1 }));
  };

  const updateCartItemCount = (newAmount, itemId) => {
    setCartItems((prev) => ({ ...prev, [itemId]: newAmount }));
  };

  const checkout = () => {
    setCartItems(getDefaultCart());
  };

  const contextValue = {
    cartItems,
    getTotalCartAmount,
    addToCart,
    removeFromCart,
    updateCartItemCount,
    checkout
  };
  return (
    <ShopContext.Provider value={contextValue}>
      {props.children}
    </ShopContext.Provider>
  )
}

将Provider传递给layout.js。
使用ShopContextProvider将整个内容包围起来。

import { Inter } from 'next/font/google'
import './globals.css'
import Navbar from './components/navbar'
+ import { ShopContextProvider } from '@/context/shop-context'


const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
+        <ShopContextProvider>
           <Navbar />
           {children}
+        </ShopContextProvider>
      </body>
    </html>
  )
}

将Shop组件拆分并创建Item组件

"use client";
import React, { useEffect, useState } from "react";
import "./shop.css";
+ import Item from "./item";

function Shop() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch("https://fakestoreapi.com/products")
      .then((res) => res.json())
      .then((json) => {
        console.log(json);
        setItems(json);
      });
  }, []);

  return (
    <div className="shop">
      <div className="shopTitle">
        <h1>E-commerce Shop</h1>
      </div>

      <div className="products">
        {items.map((item) => (
+          <Item
+            id={item.id}
+            title={item.title}
+            image={item.image}
+            price={item.price}
+            description={item.description}
+            category={item.category}
+            key={item.id}
+          />
        ))}
      </div>
    </div>
  );
}

export default Shop;

从shop.jsx接收props,创建Item组件。

"use client";
import React from "react";

function Item({ id, title, image, price, description, category }) {
  return (
    <div key={id} className="product">
      <div className="content">
        <p className="title">
          {id}. {title}
        </p>
        <img src={image} alt={title} className="image" />
        <p className="price">${price}</p>
        <p className="description">{description}</p>
        <p className="category">{category}</p>
      </div>
      <button className="addToCartBtn">ADD</button>
    </div>
  );
}

export default Item;

步骤8 实现购物车按钮

我們將在Item組件中實現購物車按鈕。

使用useContext钩子,获取全局状态变量。
此外,将props作为一个对象进行常量声明,采用了将其属性一并存储的方式。
定义常量{id, title, image, price, description, category} = props。

"use client";
+ import { ShopContext } from "@/context/shop-context";
+ import React, { useContext } from "react";

function Item(props) {
  const { id, title, image, price, description, category } = props;
+  const { addToCart, cartItems } = useContext(ShopContext);
+  const cartItemCount = cartItems[id];

  return (
    <div key={id} className="product">
      <div className="content">
        <p className="title">
          {id}. {title}
        </p>
        <img src={image} alt={title} className="image" />
        <p className="price">${price}</p>
        <p className="description">{description}</p>
        <p className="category">{category}</p>
      </div>
+      <button className="addToCartBtn" onClick={() => addToCart(id)}>
+        カートに追加する {cartItemCount > 0 && <span>({cartItemCount}個)</span>}
+      </button>
    </div>
  );
}

export default Item;

第九步 实现购物车逻辑

我們將用useContext來實現之前暫時存放的數據。

将购物车中的商品拆分并在cartItem组件中实现。
使用data属性,将props传递给cartItem组件。

+ "use client";

+ import React, { useContext } from "react";
  import "./cart.css";
+ import CartItem from "./cartItem";
+ import { ShopContext } from "@/context/shop-context";

const Cart = () => {
+  const { items, cartItems } = useContext(ShopContext);

  return (
    <div className="cart">
      <div>
        <h1>カートの商品</h1>
      </div>
      <div className="cart">
+        {items.map((item) => {
+          if (cartItems[item.id] !== 0) {
+            return <CartItem data={item} />;
+          }
+        })}
      </div>

      <div className="checkout">
        <p>小計: xxxx</p>
        <button>買い物を続ける</button>
        <button>チェックアウト</button>
      </div>
    </div>
  );
};

export default Cart;

"use client";
import { ShopContext } from "@/context/shop-context";
import React, { useContext } from "react";

function CartItem(props) {
  const { id, title, image, price } = props.data;
  const { cartItems, addToCart, removeFromCart, updateCartItemCount } =
    useContext(ShopContext);

  return (
    <div className="cartItem">
      <img src={image} />
      <div className="description">
        <p className="cartItemTitle">{title}</p>
        <p>$ {price}</p>
        <div className="countHandler">
          <button onClick={() => removeFromCart(id)}> - </button>
          <input
            value={cartItems[id]}
            onChange={(e) => updateCartItemCount(Number(e.target.value), id)}
          />
          <button onClick={() => addToCart(id)}> + </button>
        </div>
      </div>
    </div>
  );
}

export default CartItem;

第十步 实现显示总金额和清空购物车功能

"use client";

import React, { useContext } from "react";
import "./cart.css";
import CartItem from "./cartItem";
import { ShopContext } from "@/context/shop-context";

const Cart = () => {
+ const { items, cartItems, getTotalCartAmount, checkout } =
    useContext(ShopContext);
+ // 小数点第2位で四捨五入
+ const totalAmount = Math.round(getTotalCartAmount() * 100) / 100;

  return (
    <div className="cart">
      <div>
        <h1>カートの商品</h1>
      </div>
      <div className="cart">
        {items.map((item) => {
          if (cartItems[item.id] !== 0) {
            return <CartItem data={item} key={item.id} />;
          }
        })}
      </div>

+      {totalAmount > 0 ? (
+        <div className="checkout">
+          <p className="total">合計: ${totalAmount}</p>
+          <button
+            onClick={() => {
+              checkout();
+            }}
+          >
+            カートを空にする
+          </button>
+        </div>
+     ) : (
+       <h1> cart is empty</h1>
+     )}
    </div>
  );
};

export default Cart;
"use client";
import { ShopContext } from "@/context/shop-context";
import React, { useContext } from "react";

function CartItem(props) {
  const { id, title, image, price } = props.data;
  const { cartItems, addToCart, removeFromCart, updateCartItemCount } =
    useContext(ShopContext);

  return (
    <div className="cartItem">
      <img src={image} />
      <div className="description">
        <p className="cartItemTitle">{title}</p>
        <p>$ {price}</p>
        <div className="countHandler">
          <button onClick={() => removeFromCart(id)}> - </button>
          <input
            value={cartItems[id]}
            onChange={(e) => updateCartItemCount(Number(e.target.value), id)}
          />
          <button onClick={() => addToCart(id)}> + </button>
        </div>
+        <p className="subtotal">小計: ${price * cartItems[id]}</p>
      </div>
    </div>
  );
}

export default CartItem;

已完成!

总结

使用Next.js,您能够轻松地创建一个购物网站,并且能够轻松部署。
请尝试添加登录功能等,希望您能进行一些创意调整!

如果您有任何不明白或有疑问的地方,请随时提问!我会尽力回答。

最后

我们公司正在积极招聘工程师!!

在株式会社ワクト,想把你的「兴奋」变成工作吗?

 

bannerAds