19. 实战项目三电商网站概述电商网站是一个完整的全栈项目涵盖商品展示、购物车、订单管理、用户认证等核心功能。通过这个项目你将掌握复杂状态管理、购物车逻辑、支付集成等高级技能。维度内容What完整电商网站包含商品列表、购物车、订单Why掌握复杂状态管理、购物车逻辑When高级实战练习Where完整全栈项目Who需要商业项目经验的开发者How实现商品浏览、购物车、下单流程1. 项目需求1.1 功能列表✅ 商品列表展示网格/列表视图✅ 商品详情页面✅ 商品搜索/筛选/排序✅ 购物车添加/删除/修改数量✅ 用户认证登录/注册✅ 下单流程✅ 订单列表/详情✅ 用户个人中心✅ 支付集成模拟1.2 技术栈React 19TypeScriptZustand (状态管理)React Router v6Tailwind CSSJSON Server (Mock API)2. 项目结构ecommerce-app/ ├── src/ │ ├── components/ │ │ ├── Layout/ │ │ ├── ProductCard.tsx │ │ ├── ProductGrid.tsx │ │ ├── CartItem.tsx │ │ ├── CartSummary.tsx │ │ ├── SearchBar.tsx │ │ ├── FilterSidebar.tsx │ │ └── Pagination.tsx │ ├── pages/ │ │ ├── HomePage.tsx │ │ ├── ProductsPage.tsx │ │ ├── ProductDetailPage.tsx │ │ ├── CartPage.tsx │ │ ├── CheckoutPage.tsx │ │ ├── OrdersPage.tsx │ │ ├── OrderDetailPage.tsx │ │ ├── LoginPage.tsx │ │ └── ProfilePage.tsx │ ├── stores/ │ │ ├── cartStore.ts │ │ ├── authStore.ts │ │ └── filterStore.ts │ ├── hooks/ │ │ ├── useProducts.ts │ │ ├── useProduct.ts │ │ └── useOrders.ts │ ├── api/ │ │ └── client.ts │ ├── types/ │ │ └── index.ts │ ├── utils/ │ │ └── format.ts │ ├── App.tsx │ └── main.tsx ├── db.json └── package.json3. 代码实现3.1 类型定义// src/types/index.ts export interface Product { id: string; name: string; description: string; price: number; originalPrice?: number; images: string[]; category: string; tags: string[]; stock: number; rating: number; reviewCount: number; isNew?: boolean; isHot?: boolean; } export interface CartItem { product: Product; quantity: number; } export interface Order { id: string; userId: string; items: CartItem[]; totalAmount: number; status: pending | paid | shipped | delivered | cancelled; address: Address; paymentMethod: string; createdAt: string; } export interface Address { name: string; phone: string; province: string; city: string; district: string; detail: string; } export interface User { id: string; name: string; email: string; avatar?: string; }3.2 Zustand 状态管理// src/stores/cartStore.ts import { create } from zustand; import { persist } from zustand/middleware; import { Product, CartItem } from ../types; interface CartState { items: CartItem[]; addItem: (product: Product, quantity?: number) void; removeItem: (productId: string) void; updateQuantity: (productId: string, quantity: number) void; clearCart: () void; getTotalItems: () number; getSubtotal: () number; } export const useCartStore createCartState()( persist( (set, get) ({ items: [], addItem: (product, quantity 1) { const { items } get(); const existingItem items.find(item item.product.id product.id); if (existingItem) { set({ items: items.map(item item.product.id product.id ? { ...item, quantity: item.quantity quantity } : item ), }); } else { set({ items: [...items, { product, quantity }] }); } }, removeItem: (productId) { set({ items: get().items.filter(item item.product.id ! productId) }); }, updateQuantity: (productId, quantity) { if (quantity 0) { get().removeItem(productId); return; } set({ items: get().items.map(item item.product.id productId ? { ...item, quantity } : item ), }); }, clearCart: () set({ items: [] }), getTotalItems: () { return get().items.reduce((sum, item) sum item.quantity, 0); }, getSubtotal: () { return get().items.reduce( (sum, item) sum item.product.price * item.quantity, 0 ); }, }), { name: cart-storage } ) ); // src/stores/authStore.ts import { create } from zustand; import { persist } from zustand/middleware; import { User } from ../types; interface AuthState { user: User | null; token: string | null; login: (email: string, password: string) Promisevoid; register: (name: string, email: string, password: string) Promisevoid; logout: () void; isLoading: boolean; error: string | null; } export const useAuthStore createAuthState()( persist( (set) ({ user: null, token: null, isLoading: false, error: null, login: async (email, password) { set({ isLoading: true, error: null }); try { const response await fetch(/api/auth/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ email, password }), }); const data await response.json(); set({ user: data.user, token: data.token, isLoading: false }); } catch (error) { set({ error: 登录失败, isLoading: false }); } }, register: async (name, email, password) { set({ isLoading: true, error: null }); try { const response await fetch(/api/auth/register, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name, email, password }), }); const data await response.json(); set({ user: data.user, token: data.token, isLoading: false }); } catch (error) { set({ error: 注册失败, isLoading: false }); } }, logout: () set({ user: null, token: null }), }), { name: auth-storage } ) );3.3 购物车组件// src/components/CartItem.tsx import { CartItem as CartItemType } from ../types; import { useCartStore } from ../stores/cartStore; interface CartItemProps { item: CartItemType; } export default function CartItem({ item }: CartItemProps) { const { updateQuantity, removeItem } useCartStore(); const { product, quantity } item; return ( div classNameflex gap-4 py-4 border-b img src{product.images[0]} alt{product.name} classNamew-24 h-24 object-cover rounded / div classNameflex-1 h3 classNamefont-semibold{product.name}/h3 p classNametext-gray-500 text-sm{product.category}/p p classNametext-blue-600 font-bold¥{product.price}/p /div div classNameflex items-center gap-2 button onClick{() updateQuantity(product.id, quantity - 1)} classNamew-8 h-8 border rounded hover:bg-gray-100 - /button span classNamew-12 text-center{quantity}/span button onClick{() updateQuantity(product.id, quantity 1)} classNamew-8 h-8 border rounded hover:bg-gray-100 /button button onClick{() removeItem(product.id)} classNameml-4 text-red-500 hover:text-red-700 删除 /button /div /div ); } // src/components/CartSummary.tsx import { useCartStore } from ../stores/cartStore; interface CartSummaryProps { onCheckout: () void; } export default function CartSummary({ onCheckout }: CartSummaryProps) { const { getSubtotal, items } useCartStore(); const subtotal getSubtotal(); const shipping subtotal 200 ? 0 : 10; const total subtotal shipping; if (items.length 0) return null; return ( div classNamebg-gray-50 p-6 rounded-lg h3 classNametext-lg font-bold mb-4订单摘要/h3 div classNamespace-y-2 mb-4 div classNameflex justify-between span商品金额/span span¥{subtotal.toFixed(2)}/span /div div classNameflex justify-between span运费/span span{shipping 0 ? 免运费 : ¥${shipping.toFixed(2)}}/span /div div classNameborder-t pt-2 mt-2 div classNameflex justify-between font-bold text-lg span总计/span span classNametext-blue-600¥{total.toFixed(2)}/span /div /div /div button onClick{onCheckout} classNamew-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 去结算 /button /div ); }3.4 商品组件// src/components/ProductCard.tsx import { Link } from react-router-dom; import { Product } from ../types; import { useCartStore } from ../stores/cartStore; interface ProductCardProps { product: Product; } export default function ProductCard({ product }: ProductCardProps) { const { addItem } useCartStore(); const handleAddToCart (e: React.MouseEvent) { e.preventDefault(); addItem(product); }; return ( Link to{/products/${product.id}} classNamegroup div classNamebg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow div classNamerelative img src{product.images[0]} alt{product.name} classNamew-full h-64 object-cover group-hover:scale-105 transition-transform duration-300 / {product.isNew ( span classNameabsolute top-2 left-2 px-2 py-1 bg-green-500 text-white text-xs rounded 新品 /span )} {product.isHot ( span classNameabsolute top-2 left-2 px-2 py-1 bg-red-500 text-white text-xs rounded 热卖 /span )} /div div classNamep-4 h3 classNamefont-semibold text-lg mb-1 group-hover:text-blue-600 {product.name} /h3 p classNametext-gray-500 text-sm mb-2{product.category}/p div classNameflex items-center gap-2 mb-3 span classNametext-xl font-bold text-blue-600¥{product.price}/span {product.originalPrice ( span classNametext-gray-400 line-through¥{product.originalPrice}/span )} /div div classNameflex items-center justify-between div classNameflex items-center gap-1 span classNametext-yellow-500★/span span{product.rating}/span span classNametext-gray-400({product.reviewCount})/span /div button onClick{handleAddToCart} classNamepx-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 加入购物车 /button /div /div /div /Link ); }3.5 购物车页面// src/pages/CartPage.tsx import { useNavigate } from react-router-dom; import { useCartStore } from ../stores/cartStore; import CartItem from ../components/CartItem; import CartSummary from ../components/CartSummary; export default function CartPage() { const navigate useNavigate(); const { items, clearCart } useCartStore(); const handleCheckout () { navigate(/checkout); }; if (items.length 0) { return ( div classNametext-center py-12 div classNametext-6xl mb-4/div h2 classNametext-2xl font-bold mb-2购物车是空的/h2 p classNametext-gray-500 mb-6去添加一些商品吧/p button onClick{() navigate(/products)} classNamepx-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 去购物 /button /div ); } return ( div classNamecontainer mx-auto px-4 py-8 h1 classNametext-3xl font-bold mb-8购物车/h1 div classNameflex flex-col lg:flex-row gap-8 div classNameflex-1 {items.map(item ( CartItem key{item.product.id} item{item} / ))} div classNamemt-4 flex justify-end button onClick{clearCart} classNametext-red-500 hover:text-red-700 清空购物车 /button /div /div div classNamelg:w-80 CartSummary onCheckout{handleCheckout} / /div /div /div ); }3.6 结算页面// src/pages/CheckoutPage.tsx import { useState } from react; import { useNavigate } from react-router-dom; import { useCartStore } from ../stores/cartStore; import { useAuthStore } from ../stores/authStore; import { Address } from ../types; export default function CheckoutPage() { const navigate useNavigate(); const { user } useAuthStore(); const { items, getSubtotal, clearCart } useCartStore(); const [address, setAddress] useStateAddress({ name: user?.name || , phone: , province: , city: , district: , detail: , }); const [paymentMethod, setPaymentMethod] useState(wechat); const [isSubmitting, setIsSubmitting] useState(false); const subtotal getSubtotal(); const shipping subtotal 200 ? 0 : 10; const total subtotal shipping; const handleSubmit async (e: React.FormEvent) { e.preventDefault(); setIsSubmitting(true); // 模拟下单 await new Promise(resolve setTimeout(resolve, 1500)); // 创建订单 const order { id: Date.now().toString(), userId: user?.id, items, totalAmount: total, status: pending, address, paymentMethod, createdAt: new Date().toISOString(), }; // 保存订单到 localStorage const orders JSON.parse(localStorage.getItem(orders) || []); orders.push(order); localStorage.setItem(orders, JSON.stringify(orders)); // 清空购物车 clearCart(); // 跳转到支付页面 navigate(/orders/${order.id}/payment); }; return ( div classNamecontainer mx-auto px-4 py-8 h1 classNametext-3xl font-bold mb-8结算/h1 div classNameflex flex-col lg:flex-row gap-8 div classNameflex-1 form onSubmit{handleSubmit} classNamespace-y-6 {/* 收货信息 */} div classNamebg-white p-6 rounded-lg shadow h2 classNametext-xl font-bold mb-4收货信息/h2 div classNamegrid grid-cols-2 gap-4 div label classNameblock mb-1姓名/label input value{address.name} onChange{(e) setAddress({ ...address, name: e.target.value })} classNamew-full border rounded p-2 required / /div div label classNameblock mb-1电话/label input value{address.phone} onChange{(e) setAddress({ ...address, phone: e.target.value })} classNamew-full border rounded p-2 required / /div div label classNameblock mb-1省份/label input value{address.province} onChange{(e) setAddress({ ...address, province: e.target.value })} classNamew-full border rounded p-2 / /div div label classNameblock mb-1城市/label input value{address.city} onChange{(e) setAddress({ ...address, city: e.target.value })} classNamew-full border rounded p-2 / /div div classNamecol-span-2 label classNameblock mb-1详细地址/label input value{address.detail} onChange{(e) setAddress({ ...address, detail: e.target.value })} classNamew-full border rounded p-2 / /div /div /div {/* 支付方式 */} div classNamebg-white p-6 rounded-lg shadow h2 classNametext-xl font-bold mb-4支付方式/h2 div classNamespace-y-2 label classNameflex items-center gap-2 input typeradio valuewechat checked{paymentMethod wechat} onChange{(e) setPaymentMethod(e.target.value)} / span微信支付/span /label label classNameflex items-center gap-2 input typeradio valuealipay checked{paymentMethod alipay} onChange{(e) setPaymentMethod(e.target.value)} / span支付宝/span /label /div /div button typesubmit disabled{isSubmitting} classNamew-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 {isSubmitting ? 提交中... : 提交订单 ¥${total.toFixed(2)}} /button /form /div div classNamelg:w-80 CartSummary onCheckout{() {}} / /div /div /div ); }3.7 Mock 数据// db.json{products:[{id:1,name:智能手机 Pro,description:最新款智能手机配备旗舰处理器和超清屏幕,price:5999,originalPrice:6999,images:[/images/phone-1.jpg],category:电子产品,tags:[手机,旗舰],stock:100,rating:4.8,reviewCount:1234,isNew:true}],users:[{id:1,name:张三,email:zhangexample.com,password:123456}]}4. 项目运行# 安装依赖npminstallzustand react-router-dom# 启动项目npmrun dev5. 总结核心知识点知识点应用Zustand购物车状态、认证状态购物车逻辑添加/删除/数量修改/金额计算订单流程收货信息 → 支付 → 订单确认持久化localStorage 保存购物车和登录状态