Appearance
14.3 实战 3:商品列表展示页面
本章节将实现一个商品列表展示页面,包括商品数据的存储、展示和简单的筛选功能。
项目结构
product-list/
├── db.php # 数据库连接文件
├── index.php # 商品列表页面
├── product.php # 商品详情页面
└── css/
└── style.css # 样式文件1. 数据库配置
创建 db.php 文件,用于数据库连接:
php
<?php
// db.php
function getDbConnection() {
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "php_tutorial";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检查连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 设置字符集
$conn->set_charset("utf8mb4");
return $conn;
}
?>2. 数据库表结构
创建 products 表:
sql
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
description TEXT,
image VARCHAR(255),
category VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据
INSERT INTO products (name, price, description, image, category) VALUES
('iPhone 13', 5999.99, '苹果 iPhone 13 智能手机', 'https://example.com/iphone13.jpg', '手机'),
('MacBook Pro', 12999.99, '苹果 MacBook Pro 笔记本电脑', 'https://example.com/macbook.jpg', '电脑'),
('AirPods Pro', 1999.99, '苹果 AirPods Pro 无线耳机', 'https://example.com/airpods.jpg', '耳机'),
('iPad Pro', 6999.99, '苹果 iPad Pro 平板电脑', 'https://example.com/ipad.jpg', '平板'),
('Apple Watch', 2999.99, '苹果 Apple Watch 智能手表', 'https://example.com/watch.jpg', '手表'),
('AirPods Max', 4399.99, '苹果 AirPods Max 头戴式耳机', 'https://example.com/airpods-max.jpg', '耳机'),
('Mac mini', 6299.99, '苹果 Mac mini 台式电脑', 'https://example.com/mac-mini.jpg', '电脑'),
('HomePod mini', 799.99, '苹果 HomePod mini 智能音箱', 'https://example.com/homepod.jpg', '音箱');3. 样式文件
创建 css/style.css 文件:
css
/* style.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.filter {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.filter h2 {
margin-bottom: 15px;
font-size: 18px;
}
.filter form {
display: flex;
gap: 10px;
align-items: center;
}
.filter input[type="text"],
.filter select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.filter button {
background-color: #4CAF50;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.filter button:hover {
background-color: #45a049;
}
.products {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.product-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease;
}
.product-card:hover {
transform: translateY(-5px);
}
.product-image {
height: 200px;
overflow: hidden;
background-color: #f0f0f0;
}
.product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.product-info {
padding: 15px;
}
.product-name {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.product-price {
font-size: 20px;
font-weight: bold;
color: #e53935;
margin-bottom: 10px;
}
.product-description {
font-size: 14px;
color: #666;
margin-bottom: 15px;
line-height: 1.4;
}
.product-category {
font-size: 12px;
color: #999;
margin-bottom: 15px;
}
.product-link {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
transition: background-color 0.3s ease;
}
.product-link:hover {
background-color: #45a049;
}
.pagination {
margin-top: 30px;
text-align: center;
}
.pagination a {
display: inline-block;
padding: 8px 16px;
margin: 0 5px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
text-decoration: none;
color: #333;
transition: background-color 0.3s ease;
}
.pagination a:hover {
background-color: #f0f0f0;
}
.pagination .active {
background-color: #4CAF50;
color: white;
border: 1px solid #4CAF50;
}
/* 商品详情页面样式 */
.product-detail {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
gap: 30px;
}
.product-detail-image {
flex: 1;
max-width: 400px;
}
.product-detail-image img {
width: 100%;
border-radius: 8px;
}
.product-detail-info {
flex: 1;
}
.product-detail-name {
font-size: 24px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.product-detail-price {
font-size: 28px;
font-weight: bold;
color: #e53935;
margin-bottom: 20px;
}
.product-detail-description {
font-size: 16px;
color: #666;
margin-bottom: 20px;
line-height: 1.6;
}
.product-detail-category {
font-size: 14px;
color: #999;
margin-bottom: 20px;
}
.back-link {
display: inline-block;
background-color: #6c757d;
color: white;
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
margin-top: 20px;
transition: background-color 0.3s ease;
}
.back-link:hover {
background-color: #5a6268;
}
/* 响应式设计 */
@media (max-width: 768px) {
.filter form {
flex-direction: column;
align-items: stretch;
}
.product-detail {
flex-direction: column;
}
.product-detail-image {
max-width: 100%;
}
}4. 商品列表页面
创建 index.php 文件:
php
<?php
// index.php
require_once 'db.php';
$conn = getDbConnection();
// 分页设置
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 6;
$offset = ($page - 1) * $perPage;
// 筛选条件
$search = isset($_GET['search']) ? $_GET['search'] : '';
$category = isset($_GET['category']) ? $_GET['category'] : '';
// 构建查询
$sql = "SELECT * FROM products WHERE 1=1";
$params = [];
$types = '';
if (!empty($search)) {
$sql .= " AND (name LIKE ? OR description LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$types .= "ss";
}
if (!empty($category)) {
$sql .= " AND category = ?";
$params[] = $category;
$types .= "s";
}
// 获取总数
$countSql = "SELECT COUNT(*) as total FROM products WHERE 1=1";
if (!empty($search)) {
$countSql .= " AND (name LIKE ? OR description LIKE ?)";
}
if (!empty($category)) {
$countSql .= " AND category = ?";
}
$countStmt = $conn->prepare($countSql);
if (!empty($params)) {
$countStmt->bind_param($types, ...$params);
}
$countStmt->execute();
$countResult = $countStmt->get_result();
$countRow = $countResult->fetch_assoc();
$totalProducts = $countRow['total'];
$totalPages = ceil($totalProducts / $perPage);
$countStmt->close();
// 获取商品列表
$sql .= " ORDER BY created_at DESC LIMIT ? OFFSET ?";
$params[] = $perPage;
$params[] = $offset;
$types .= "ii";
$stmt = $conn->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$products = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$products[] = $row;
}
}
// 获取所有分类
$categorySql = "SELECT DISTINCT category FROM products";
$categoryResult = $conn->query($categorySql);
$categories = [];
if ($categoryResult->num_rows > 0) {
while ($row = $categoryResult->fetch_assoc()) {
$categories[] = $row['category'];
}
}
$stmt->close();
$conn->close();
?>
<!DOCTYPE html>
<html>
<head>
<title>商品列表</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<h1>商品列表</h1>
<!-- 筛选表单 -->
<div class="filter">
<h2>筛选</h2>
<form action="" method="get">
<input type="text" name="search" placeholder="搜索商品" value="<?php echo htmlspecialchars($search); ?>">
<select name="category">
<option value="">全部分类</option>
<?php foreach ($categories as $cat): ?>
<option value="<?php echo htmlspecialchars($cat); ?>" <?php echo $category === $cat ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($cat); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit">筛选</button>
</form>
</div>
<!-- 商品列表 -->
<div class="products">
<?php if (empty($products)): ?>
<p style="grid-column: 1 / -1; text-align: center; padding: 40px;">暂无商品</p>
<?php else: ?>
<?php foreach ($products as $product): ?>
<div class="product-card">
<div class="product-image">
<img src="<?php echo htmlspecialchars($product['image']); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>">
</div>
<div class="product-info">
<div class="product-name"><?php echo htmlspecialchars($product['name']); ?></div>
<div class="product-price">¥<?php echo number_format($product['price'], 2); ?></div>
<div class="product-description"><?php echo substr(htmlspecialchars($product['description']), 0, 100); ?>...</div>
<div class="product-category">分类: <?php echo htmlspecialchars($product['category']); ?></div>
<a href="product.php?id=<?php echo $product['id']; ?>" class="product-link">查看详情</a>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?php echo $page - 1; ?>&search=<?php echo urlencode($search); ?>&category=<?php echo urlencode($category); ?>">上一页</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<a href="?page=<?php echo $i; ?>&search=<?php echo urlencode($search); ?>&category=<?php echo urlencode($category); ?>" class="<?php echo $i == $page ? 'active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
<?php if ($page < $totalPages): ?>
<a href="?page=<?php echo $page + 1; ?>&search=<?php echo urlencode($search); ?>&category=<?php echo urlencode($category); ?>">下一页</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</body>
</html>5. 商品详情页面
创建 product.php 文件:
php
<?php
// product.php
require_once 'db.php';
$conn = getDbConnection();
$productId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
// 获取商品详情
$stmt = $conn->prepare("SELECT * FROM products WHERE id = ?");
$stmt->bind_param("i", $productId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
header('Location: index.php?error=商品不存在');
exit;
}
$product = $result->fetch_assoc();
$stmt->close();
$conn->close();
?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo htmlspecialchars($product['name']); ?> - 商品详情</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<h1>商品详情</h1>
<div class="product-detail">
<div class="product-detail-image">
<img src="<?php echo htmlspecialchars($product['image']); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>">
</div>
<div class="product-detail-info">
<div class="product-detail-name"><?php echo htmlspecialchars($product['name']); ?></div>
<div class="product-detail-price">¥<?php echo number_format($product['price'], 2); ?></div>
<div class="product-detail-category">分类: <?php echo htmlspecialchars($product['category']); ?></div>
<div class="product-detail-description"><?php echo nl2br(htmlspecialchars($product['description'])); ?></div>
<a href="index.php" class="back-link">返回列表</a>
</div>
</div>
</div>
</body>
</html>6. 运行项目
- 确保数据库已创建并创建了
products表,插入了测试数据 - 启动本地服务器
- 访问
http://localhost/product-list/index.php - 查看商品列表,使用搜索和分类筛选功能
- 点击商品卡片查看商品详情
7. 功能扩展
1. 添加商品排序功能
修改 index.php 文件,添加排序功能:
php
// 排序选项
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'created_at';
$order = isset($_GET['order']) ? $_GET['order'] : 'desc';
// 构建排序语句
$validSorts = ['name', 'price', 'created_at'];
if (!in_array($sort, $validSorts)) {
$sort = 'created_at';
}
$validOrders = ['asc', 'desc'];
if (!in_array($order, $validOrders)) {
$order = 'desc';
}
// 修改查询语句
$sql .= " ORDER BY $sort $order LIMIT ? OFFSET ?";
// 在筛选表单中添加排序选项
<div class="filter">
<h2>筛选</h2>
<form action="" method="get">
<input type="text" name="search" placeholder="搜索商品" value="<?php echo htmlspecialchars($search); ?>">
<select name="category">
<option value="">全部分类</option>
<?php foreach ($categories as $cat): ?>
<option value="<?php echo htmlspecialchars($cat); ?>" <?php echo $category === $cat ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($cat); ?>
</option>
<?php endforeach; ?>
</select>
<select name="sort">
<option value="created_at" <?php echo $sort === 'created_at' ? 'selected' : ''; ?>>最新上架</option>
<option value="price" <?php echo $sort === 'price' ? 'selected' : ''; ?>>价格</option>
<option value="name" <?php echo $sort === 'name' ? 'selected' : ''; ?>>名称</option>
</select>
<select name="order">
<option value="desc" <?php echo $order === 'desc' ? 'selected' : ''; ?>>降序</option>
<option value="asc" <?php echo $order === 'asc' ? 'selected' : ''; ?>>升序</option>
</select>
<button type="submit">筛选</button>
</form>
</div>
// 更新分页链接
<a href="?page=<?php echo $page - 1; ?>&search=<?php echo urlencode($search); ?>&category=<?php echo urlencode($category); ?>&sort=<?php echo urlencode($sort); ?>&order=<?php echo urlencode($order); ?>">上一页</a>
// 其他分页链接也需要更新2. 添加商品图片轮播
修改 product.php 文件,添加图片轮播功能:
php
// 假设商品有多个图片
$productImages = [
$product['image'],
str_replace('.jpg', '-1.jpg', $product['image']),
str_replace('.jpg', '-2.jpg', $product['image'])
];
// 在商品详情页面添加轮播
<div class="product-detail-image">
<div class="image-slider">
<?php foreach ($productImages as $image): ?>
<div class="slide">
<img src="<?php echo htmlspecialchars($image); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>">
</div>
<?php endforeach; ?>
</div>
<div class="slider-controls">
<button class="prev">上一张</button>
<button class="next">下一张</button>
</div>
</div>
// 添加轮播样式和脚本
<style>
.image-slider {
position: relative;
height: 400px;
overflow: hidden;
}
.slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s ease;
}
.slide.active {
opacity: 1;
}
.slider-controls {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
.slider-controls button {
padding: 5px 10px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
<script>
let currentSlide = 0;
const slides = document.querySelectorAll('.slide');
const prevBtn = document.querySelector('.prev');
const nextBtn = document.querySelector('.next');
function showSlide(index) {
slides.forEach((slide, i) => {
slide.classList.toggle('active', i === index);
});
currentSlide = index;
}
prevBtn.addEventListener('click', () => {
let index = currentSlide - 1;
if (index < 0) {
index = slides.length - 1;
}
showSlide(index);
});
nextBtn.addEventListener('click', () => {
let index = currentSlide + 1;
if (index >= slides.length) {
index = 0;
}
showSlide(index);
});
// 初始化
showSlide(0);
</script>3. 添加购物车功能
创建 cart.php 文件,实现简单的购物车功能:
php
<?php
// cart.php
session_start();
// 初始化购物车
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
// 添加商品到购物车
if (isset($_GET['add'])) {
$productId = (int)$_GET['add'];
if (!isset($_SESSION['cart'][$productId])) {
$_SESSION['cart'][$productId] = 1;
} else {
$_SESSION['cart'][$productId]++;
}
header('Location: cart.php?success=商品已添加到购物车');
exit;
}
// 从购物车移除商品
if (isset($_GET['remove'])) {
$productId = (int)$_GET['remove'];
if (isset($_SESSION['cart'][$productId])) {
unset($_SESSION['cart'][$productId]);
}
header('Location: cart.php?success=商品已从购物车移除');
exit;
}
// 更新商品数量
if (isset($_POST['update'])) {
foreach ($_POST['quantity'] as $productId => $quantity) {
$quantity = (int)$quantity;
if ($quantity > 0) {
$_SESSION['cart'][$productId] = $quantity;
} else {
unset($_SESSION['cart'][$productId]);
}
}
header('Location: cart.php?success=购物车已更新');
exit;
}
// 获取购物车中的商品
require_once 'db.php';
$conn = getDbConnection();
$cartItems = [];
$totalPrice = 0;
if (!empty($_SESSION['cart'])) {
$productIds = implode(',', array_keys($_SESSION['cart']));
$sql = "SELECT * FROM products WHERE id IN ($productIds)";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while ($product = $result->fetch_assoc()) {
$quantity = $_SESSION['cart'][$product['id']];
$subtotal = $product['price'] * $quantity;
$totalPrice += $subtotal;
$cartItems[] = [
'id' => $product['id'],
'name' => $product['name'],
'price' => $product['price'],
'quantity' => $quantity,
'subtotal' => $subtotal,
'image' => $product['image']
];
}
}
}
$conn->close();
?>
<!DOCTYPE html>
<html>
<head>
<title>购物车</title>
<link rel="stylesheet" href="css/style.css">
<style>
.cart {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
}
.cart-item {
display: flex;
gap: 20px;
padding: 20px 0;
border-bottom: 1px solid #eee;
}
.cart-item:last-child {
border-bottom: none;
}
.cart-item-image {
width: 100px;
height: 100px;
overflow: hidden;
border-radius: 4px;
}
.cart-item-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cart-item-info {
flex: 1;
}
.cart-item-name {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
.cart-item-price {
font-size: 14px;
color: #e53935;
margin-bottom: 10px;
}
.cart-item-quantity {
display: flex;
align-items: center;
gap: 10px;
}
.cart-item-quantity input {
width: 60px;
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
.cart-item-remove {
color: #dc3545;
text-decoration: none;
font-size: 14px;
}
.cart-item-remove:hover {
text-decoration: underline;
}
.cart-total {
margin-top: 30px;
text-align: right;
}
.cart-total-price {
font-size: 20px;
font-weight: bold;
color: #e53935;
}
.cart-actions {
margin-top: 20px;
display: flex;
justify-content: space-between;
}
.continue-shopping {
background-color: #6c757d;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
}
.checkout {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
}
.empty-cart {
text-align: center;
padding: 40px;
}
</style>
</head>
<body>
<div class="container">
<h1>购物车</h1>
<?php if (isset($_GET['success'])): ?>
<div class="success"><?php echo $_GET['success']; ?></div>
<?php endif; ?>
<div class="cart">
<?php if (empty($cartItems)): ?>
<div class="empty-cart">
<p>购物车为空</p>
<a href="index.php" class="continue-shopping">继续购物</a>
</div>
<?php else: ?>
<form action="" method="post">
<?php foreach ($cartItems as $item): ?>
<div class="cart-item">
<div class="cart-item-image">
<img src="<?php echo htmlspecialchars($item['image']); ?>" alt="<?php echo htmlspecialchars($item['name']); ?>">
</div>
<div class="cart-item-info">
<div class="cart-item-name"><?php echo htmlspecialchars($item['name']); ?></div>
<div class="cart-item-price">¥<?php echo number_format($item['price'], 2); ?></div>
<div class="cart-item-quantity">
<label>数量:</label>
<input type="number" name="quantity[<?php echo $item['id']; ?>]" value="<?php echo $item['quantity']; ?>" min="1">
<a href="cart.php?remove=<?php echo $item['id']; ?>" class="cart-item-remove" onclick="return confirm('确定要移除这个商品吗?');">移除</a>
</div>
</div>
<div class="cart-item-subtotal">
¥<?php echo number_format($item['subtotal'], 2); ?>
</div>
</div>
<?php endforeach; ?>
<div class="cart-actions">
<button type="submit" name="update" class="continue-shopping">更新购物车</button>
<a href="index.php" class="continue-shopping">继续购物</a>
</div>
</form>
<div class="cart-total">
<p>总计: <span class="cart-total-price">¥<?php echo number_format($totalPrice, 2); ?></span></p>
</div>
<div class="cart-actions">
<a href="checkout.php" class="checkout">去结算</a>
</div>
<?php endif; ?>
</div>
</div>
</body>
</html>修改 product.php 文件,添加添加到购物车按钮:
php
<div class="product-detail-actions">
<a href="cart.php?add=<?php echo $product['id']; ?>" class="add-to-cart" style="display: inline-block; background-color: #4CAF50; color: white; padding: 10px 20px; border-radius: 4px; text-decoration: none; font-size: 16px; margin-right: 10px;">添加到购物车</a>
<a href="index.php" class="back-link">返回列表</a>
</div>修改 index.php 文件,在商品卡片中添加添加到购物车按钮:
php
<div class="product-actions">
<a href="product.php?id=<?php echo $product['id']; ?>" class="product-link">查看详情</a>
<a href="cart.php?add=<?php echo $product['id']; ?>" class="add-to-cart" style="display: inline-block; background-color: #ff9800; color: white; padding: 8px 16px; border-radius: 4px; text-decoration: none; font-size: 14px; margin-top: 10px; width: 100%; text-align: center;">添加到购物车</a>
</div>8. 注意事项
安全性:
- 使用
htmlspecialchars防止 XSS 攻击 - 使用预处理语句防止 SQL 注入
- 验证用户输入
- 使用
性能:
- 使用分页减少数据传输
- 优化数据库查询
- 合理使用会话
用户体验:
- 提供清晰的错误提示
- 显示操作成功的反馈
- 友好的界面设计
- 响应式设计,适配不同屏幕尺寸
功能完整性:
- 实现商品列表展示
- 实现商品详情页
- 实现搜索和筛选功能
- 实现分页功能
- 实现购物车功能
练习
- 实现完整的商品列表展示系统
- 添加商品排序功能
- 添加商品图片轮播
- 实现购物车功能
- 优化用户界面和体验
