【欢迎初学者🔰】使用Next.js构建购物网站🛒
这次的作品
GitHub的仓库
实施功能
- 商品一覧を表示(Top画面)

- ログイン・ログアウト機能(Contact画面)

- カートに追加(Cart画面)

- 小計&合計金額の表示(Cart画面)

借鉴的视频
请留意事项。
开发环境
操作系统: windows10
编辑器: VScode
终端: Bash
node.js版本: v18.17.0
(如果你熟悉TypeScript,请务必尝试使用TypeScript进行编写!)
CSS的代码如下所示:
CSS的代码sample.css
h1 {
color:red;
}
步骤1:在Vercel上配置部署环境。

在终端中运行 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登录

创建一个新项目

将Github与其他服务进行整合

建筑会自动开始。

步骤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的代码被折叠了。
.导航栏 {
宽度: 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;
.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;
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 {
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,您能够轻松地创建一个购物网站,并且能够轻松部署。
请尝试添加登录功能等,希望您能进行一些创意调整!
如果您有任何不明白或有疑问的地方,请随时提问!我会尽力回答。
最后
我们公司正在积极招聘工程师!!
在株式会社ワクト,想把你的「兴奋」变成工作吗?