第七章:项目实战 - 第三节 - Tailwind CSS 电商网站开发

server/2025/3/4 19:22:51/

本节将介绍如何使用 Tailwind CSS 开发一个现代化的电商网站,包括商品展示、购物车、结算流程等核心功能的实现。

商品列表

商品卡片组件

// components/ProductCard.tsx
interface ProductCardProps {product: {id: string;title: string;price: number;image: string;discount?: number;tags?: string[];};onAddToCart: (productId: string) => void;
}const ProductCard: React.FC<ProductCardProps> = ({ product, onAddToCart }) => {return (<div className="group relative">{/* 商品图片 */}<div className="aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-lg bg-gray-200"><imgsrc={product.image}alt={product.title}className="h-full w-full object-cover object-center group-hover:opacity-75 transition-opacity"/>{product.discount && (<div className="absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded-md text-sm font-medium">-{product.discount}%</div>)}</div>{/* 商品信息 */}<div className="mt-4 flex justify-between"><div><h3 className="text-sm text-gray-700"><a href={`/product/${product.id}`}><span aria-hidden="true" className="absolute inset-0" />{product.title}</a></h3><div className="mt-1 flex items-center space-x-2"><p className="text-lg font-medium text-gray-900">¥{product.price}</p>{product.discount && (<p className="text-sm text-gray-500 line-through">¥{(product.price * (100 + product.discount) / 100).toFixed(2)}</p>)}</div></div><buttononClick={() => onAddToCart(product.id)}className="inline-flex items-center p-2 rounded-full bg-blue-500 text-white shadow-sm hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"><svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" /></svg></button></div>{/* 商品标签 */}{product.tags && product.tags.length > 0 && (<div className="mt-2 flex flex-wrap gap-1">{product.tags.map(tag => (<spankey={tag}className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">{tag}</span>))}</div>)}</div>);
};

商品列表页面

// pages/ProductList.tsx
import { useState } from 'react';
import ProductCard from '../components/ProductCard';
import { useCart } from '../hooks/useCart';const filters = [{ id: 'category', name: '分类', options: ['全部', '电子产品', '服装', '食品'] },{ id: 'price', name: '价格', options: ['全部', '0-100', '100-500', '500+'] },// ... 更多筛选选项
];const ProductList = () => {const [activeFilters, setActiveFilters] = useState<Record<string, string>>({});const { addToCart } = useCart();return (<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">{/* 筛选器 */}<div className="py-4 border-b border-gray-200"><div className="flex flex-wrap gap-4">{filters.map(filter => (<div key={filter.id} className="relative"><selectclassName="appearance-none bg-white border border-gray-300 rounded-md py-2 pl-3 pr-8 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"value={activeFilters[filter.id] || ''}onChange={(e) => {setActiveFilters(prev => ({...prev,[filter.id]: e.target.value}));}}><option value="">{filter.name}</option>{filter.options.map(option => (<option key={option} value={option}>{option}</option>))}</select><div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"><svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" /></svg></div></div>))}</div></div>{/* 商品网格 */}<div className="mt-6 grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-4">{products.map(product => (<ProductCardkey={product.id}product={product}onAddToCart={addToCart}/>))}</div>{/* 分页 */}<div className="mt-8 flex justify-center"><nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px"><ahref="#"className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">上一页</a>{/* 页码 */}<ahref="#"className="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">1</a><ahref="#"className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">下一页</a></nav></div></div>);
};

购物车功能

购物车 Hook

// hooks/useCart.ts
import { useState, useCallback } from 'react';interface CartItem {id: string;quantity: number;price: number;title: string;image: string;
}export const useCart = () => {const [items, setItems] = useState<CartItem[]>([]);const addToCart = useCallback((product: Omit<CartItem, 'quantity'>) => {setItems(prev => {const existingItem = prev.find(item => item.id === product.id);if (existingItem) {return prev.map(item =>item.id === product.id? { ...item, quantity: item.quantity + 1 }: item);}return [...prev, { ...product, quantity: 1 }];});}, []);const removeFromCart = useCallback((productId: string) => {setItems(prev => prev.filter(item => item.id !== productId));}, []);const updateQuantity = useCallback((productId: string, quantity: number) => {setItems(prev =>prev.map(item =>item.id === productId? { ...item, quantity: Math.max(0, quantity) }: item).filter(item => item.quantity > 0));}, []);const total = items.reduce((sum, item) => sum + item.price * item.quantity,0);return {items,addToCart,removeFromCart,updateQuantity,total};
};

购物车组件

// components/Cart.tsx
import { useCart } from '../hooks/useCart';const Cart = () => {const { items, removeFromCart, updateQuantity, total } = useCart();return (<div className="fixed inset-y-0 right-0 w-96 bg-white shadow-xl"><div className="flex flex-col h-full">{/* 购物车头部 */}<div className="px-4 py-6 bg-gray-50"><h2 className="text-lg font-medium text-gray-900">购物车</h2></div>{/* 购物车列表 */}<div className="flex-1 overflow-y-auto py-6 px-4">{items.length === 0 ? (<div className="text-center py-12"><svgclassName="mx-auto h-12 w-12 text-gray-400"fill="none"viewBox="0 0 24 24"stroke="currentColor"><pathstrokeLinecap="round"strokeLinejoin="round"strokeWidth={2}d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"/></svg><p className="mt-4 text-sm text-gray-500">购物车是空的</p></div>) : (<div className="space-y-6">{items.map(item => (<div key={item.id} className="flex"><imgsrc={item.image}alt={item.title}className="h-20 w-20 flex-shrink-0 rounded-md object-cover"/><div className="ml-4 flex flex-1 flex-col"><div><div className="flex justify-between text-base font-medium text-gray-900"><h3>{item.title}</h3><p className="ml-4">¥{item.price}</p></div></div><div className="flex flex-1 items-end justify-between text-sm"><div className="flex items-center space-x-2"><buttononClick={() => updateQuantity(item.id, item.quantity - 1)}className="text-gray-500 hover:text-gray-700">-</button><span className="text-gray-500">{item.quantity}</span><buttononClick={() => updateQuantity(item.id, item.quantity + 1)}className="text-gray-500 hover:text-gray-700">+</button></div><buttononClick={() => removeFromCart(item.id)}className="font-medium text-blue-600 hover:text-blue-500">移除</button></div></div></div>))}</div>)}</div>{/* 购物车底部 */}<div className="border-t border-gray-200 py-6 px-4"><div className="flex justify-between text-base font-medium text-gray-900"><p>总计</p><p>¥{total.toFixed(2)}</p></div><p className="mt-0.5 text-sm text-gray-500">运费和税费将在结算时计算</p><div className="mt-6"><ahref="/checkout"className="flex items-center justify-center rounded-md border border-transparent bg-blue-600 px-6 py-3 text-base font-medium text-white shadow-sm hover:bg-blue-700">结算</a></div></div></div></div>);
};

结算流程

结算表单

// components/CheckoutForm.tsx
import { useState } from 'react';interface CheckoutFormProps {onSubmit: (data: CheckoutData) => void;
}interface CheckoutData {name: string;email: string;address: string;phone: string;paymentMethod: 'credit-card' | 'alipay' | 'wechat';
}const CheckoutForm: React.FC<CheckoutFormProps> = ({ onSubmit }) => {const [formData, setFormData] = useState<CheckoutData>({name: '',email: '',address: '',phone: '',paymentMethod: 'credit-card'});const handleSubmit = (e: React.FormEvent) => {e.preventDefault();onSubmit(formData);};return (<form onSubmit={handleSubmit} className="space-y-6">{/* 个人信息 */}<div className="bg-white p-6 rounded-lg shadow"><h2 className="text-lg font-medium text-gray-900 mb-4">个人信息</h2><div className="grid grid-cols-1 gap-6"><div><label htmlFor="name" className="block text-sm font-medium text-gray-700">姓名</label><inputtype="text"id="name"value={formData.name}onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"/></div><div><label htmlFor="email" className="block text-sm font-medium text-gray-700">邮箱</label><inputtype="email"id="email"value={formData.email}onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"/></div><div><label htmlFor="phone" className="block text-sm font-medium text-gray-700">电话</label><inputtype="tel"id="phone"value={formData.phone}onChange={(e) => setFormData(prev => ({ ...prev, phone: e.target.value }))}className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"/></div><div><label htmlFor="address" className="block text-sm font-medium text-gray-700">地址</label><textareaid="address"rows={3}value={formData.address}onChange={(e) => setFormData(prev => ({ ...prev, address: e.target.value }))}className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"/></div></div></div>{/* 支付方式 */}<div className="bg-white p-6 rounded-lg shadow"><h2 className="text-lg font-medium text-gray-900 mb-4">支付方式</h2><div className="space-y-4"><div className="flex items-center"><inputtype="radio"id="credit-card"name="payment-method"value="credit-card"checked={formData.paymentMethod === 'credit-card'}onChange={(e) => setFormData(prev => ({ ...prev, paymentMethod: e.target.value as CheckoutData['paymentMethod'] }))}className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"/><label htmlFor="credit-card" className="ml-3 block text-sm font-medium text-gray-700">信用卡</label></div><div className="flex items-center"><inputtype="radio"id="alipay"name="payment-method"value="alipay"checked={formData.paymentMethod === 'alipay'}onChange={(e) => setFormData(prev => ({ ...prev, paymentMethod: e.target.value as CheckoutData['paymentMethod'] }))}className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"/><label htmlFor="alipay" className="ml-3 block text-sm font-medium text-gray-700">支付宝</label></div><div className="flex items-center"><inputtype="radio"id="wechat"name="payment-method"value="wechat"checked={formData.paymentMethod === 'wechat'}onChange={(e) => setFormData(prev => ({ ...prev, paymentMethod: e.target.value as CheckoutData['paymentMethod'] }))}className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"/><label htmlFor="wechat" className="ml-3 block text-sm font-medium text-gray-700">微信支付</label></div></div></div>{/* 提交按钮 */}<div className="flex justify-end"><buttontype="submit"className="bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">提交订单</button></div></form>);
};// 订单确认页面
const OrderConfirmation = () => {const { items, total } = useCart();return (<div className="max-w-3xl mx-auto px-4 py-8"><div className="bg-white rounded-lg shadow overflow-hidden"><div className="px-6 py-4 border-b border-gray-200"><h2 className="text-xl font-medium text-gray-900">订单确认</h2></div><div className="px-6 py-4"><div className="flow-root"><ul className="divide-y divide-gray-200">{items.map(item => (<li key={item.id} className="py-4"><div className="flex items-center space-x-4"><div className="flex-shrink-0"><img src={item.image}alt={item.title}className="h-16 w-16 rounded-md object-cover"/></div><div className="flex-1 min-w-0"><p className="text-sm font-medium text-gray-900 truncate">{item.title}</p><p className="text-sm text-gray-500">数量: {item.quantity}</p></div><div className="flex-shrink-0"><p className="text-sm font-medium text-gray-900">¥{(item.price * item.quantity).toFixed(2)}</p></div></div></li>))}</ul></div><div className="mt-6 border-t border-gray-200 pt-6"><div className="flex justify-between text-base font-medium text-gray-900"><p>总计</p><p>¥{total.toFixed(2)}</p></div><p className="mt-2 text-sm text-gray-500">运费已包含</p></div></div></div></div>);
};

最佳实践

  1. 页面组织

    • 合理的组件拆分
    • 状态管理清晰
    • 复用公共组件
  2. 用户体验

    • 加载状态处理
    • 错误提示友好
    • 表单验证完善
  3. 性能优化

    • 图片懒加载
    • 组件按需加载
    • 状态更新优化
  4. 响应式设计

    • 移动端适配
    • 合理的布局
    • 交互体验优化
  5. 功能完善

    • 商品搜索
    • 订单管理
    • 用户中心

http://www.ppmy.cn/server/172411.html

相关文章

GPU架构分类

一、NVIDIA的GPU架构 NVIDIA是全球领先的GPU生产商&#xff0c;其GPU架构在图形渲染、高性能计算和人工智能等领域具有广泛应用。NVIDIA的GPU架构经历了多次迭代&#xff0c;以下是一些重要的架构&#xff1a; 1. Tesla&#xff08;特斯拉&#xff09;架构&#xff08;2006年…

react工程化开发

react工程化开发 组件化/模块化 业务组件 & 通用组件 全局命令create-react-app npm run eject npm run eject 暴露webpack配置。&#xff08;一旦暴露就无法还原&#xff09; 新增了很多依赖项 babel/core es6转成es5 react-refresh 关于刷新的插件 babel-preset-react-ap…

物联网同RFID功能形态 使用场景的替代品

在物联网&#xff08;IoT&#xff09;和自动识别技术领域&#xff0c;除了RFID标签外&#xff0c;还有一些其他技术产品可以在形态和大小上与RFID标签相似&#xff0c;同时提供类似或更强大的功能。以下是几种能够替代RFID标签的产品&#xff1a; 一、NFC标签 NFC&#xff08;…

uniapp 系统学习,从入门到实战(七)—— 网络请求与数据交互

全篇大概 3600 字(含代码)&#xff0c;建议阅读时间 25min &#x1f4da; 目录 使用uni.request发起请求封装全局请求工具破解跨域难题总结 在跨平台应用开发中&#xff0c;网络请求是连接前端与后端服务的核心环节。UniApp 提供了 uni.request 方法处理网络请求&#xff0c;但…

C++ 二叉树代码

二叉树代码&#xff0c;见下 #include <iostream> using namespace std;template<typename T> struct TreeNode{T val;TreeNode *left;TreeNode *right;TreeNode():val(0), left(NULL), right(NULL)TreeNode(T x):val(x), left(NULL), right(NULL){} };template&l…

Deepseek Api Function Calling解析(tools、tool_calls)Deepseek函数调用流程图、Python代码示例

文章目录 Function Calling介绍**核心原理**1. **动态扩展模型能力**2. **JSON结构化交互** **实现步骤**&#xff08;以支持Function Calling的模型为例&#xff09;1. **定义可用函数**2. **模型匹配与生成**3. **开发者执行函数**4. **结果反馈给模型** **DeepSeek R1的当前…

Vue核心知识:动态路由实现完整方案

在Vue中实现动态路由&#xff0c;并结合后端接口和数据库表设计&#xff0c;是一个复杂的项目&#xff0c;需要多个技术栈和步骤的配合。以下将详细描述整个实现过程&#xff0c;包括数据库设计、后端接口设计、前端路由配置以及如何实现动态路由的功能。 目录 一、需求分析二…

【OpenCV C++】以时间命名存图,自动检查存储目录,若不存在自动创建, 按下空格、回车、Q、S自动存图

文章目录 // 保存图像的函数 void saveImage(const cv::Mat& frame) {// 生成唯一文件名auto now = std::chrono::system_clock::