[Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 4: Trang Chủ, Danh Mục Sản Phẩm & Chi Tiết Sản Phẩm

23 lượt xem · 1 trả lời

1. 12:00 06/08/2026
Tiếp Phần 3 (đã có bộ khung + giao diện chung). Hôm nay bắt đầu hiển thị dữ liệu thật: trang chủ, trang danh mục, trang chi tiết sản phẩm và trang tìm kiếm. ============================== 1. FILE index.php (trang chủ) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $page_title = 'Trang chu'; // Lay danh sach danh muc de hien thi thanh menu loc $categories = $pdo->query('SELECT id, name, slug FROM categories ORDER BY name ASC')->fetchAll(); // Lay 12 san pham dang ban, moi nhat truoc $stmt = $pdo->prepare( 'SELECT id, name, slug, price, sale_price, image FROM products WHERE is_active = 1 ORDER BY created_at DESC LIMIT 12' ); $stmt->execute(); $products = $stmt->fetchAll(); require __DIR__ . '/includes/header.php'; ?> <div class="category-bar"> <a href="index.php" class="active">Tat ca</a> <?php foreach ($categories as $cat): ?> <a href="category.php?slug=<?= e($cat['slug']) ?>"><?= e($cat['name']) ?></a> <?php endforeach; ?> </div> <h1 class="page-title">San pham noi bat</h1> <div class="product-grid"> <?php foreach ($products as $p): ?> <a href="product.php?slug=<?= e($p['slug']) ?>" class="product-card"> <img src="assets/uploads/products/<?= e($p['image'] ?: 'no-image.png') ?>" alt="<?= e($p['name']) ?>"> <div class="info"> <div class="name"><?= e($p['name']) ?></div> <?php if ($p['sale_price']): ?> <span class="price"><?= format_price($p['sale_price']) ?></span> <span class="price-old"><?= format_price($p['price']) ?></span> <?php else: ?> <span class="price"><?= format_price($p['price']) ?></span> <?php endif; ?> </div> </a> <?php endforeach; ?> <?php if (empty($products)): ?> <p>Chua co san pham nao.</p> <?php endif; ?> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== 2. FILE category.php (sản phẩm theo danh mục) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $slug = $_GET['slug'] ?? ''; $stmt = $pdo->prepare('SELECT * FROM categories WHERE slug = ? LIMIT 1'); $stmt->execute([$slug]); $category = $stmt->fetch(); if (!$category) { http_response_code(404); die('Khong tim thay danh muc.'); } $page_title = $category['name']; $categories = $pdo->query('SELECT id, name, slug FROM categories ORDER BY name ASC')->fetchAll(); $stmt = $pdo->prepare( 'SELECT id, name, slug, price, sale_price, image FROM products WHERE category_id = ? AND is_active = 1 ORDER BY created_at DESC' ); $stmt->execute([$category['id']]); $products = $stmt->fetchAll(); require __DIR__ . '/includes/header.php'; ?> <div class="category-bar"> <a href="index.php">Tat ca</a> <?php foreach ($categories as $cat): ?> <a href="category.php?slug=<?= e($cat['slug']) ?>" class="<?= $cat['id'] === $category['id'] ? 'active' : '' ?>"><?= e($cat['name']) ?></a> <?php endforeach; ?> </div> <h1 class="page-title"><?= e($category['name']) ?></h1> <div class="product-grid"> <?php foreach ($products as $p): ?> <a href="product.php?slug=<?= e($p['slug']) ?>" class="product-card"> <img src="assets/uploads/products/<?= e($p['image'] ?: 'no-image.png') ?>" alt="<?= e($p['name']) ?>"> <div class="info"> <div class="name"><?= e($p['name']) ?></div> <?php if ($p['sale_price']): ?> <span class="price"><?= format_price($p['sale_price']) ?></span> <span class="price-old"><?= format_price($p['price']) ?></span> <?php else: ?> <span class="price"><?= format_price($p['price']) ?></span> <?php endif; ?> </div> </a> <?php endforeach; ?> <?php if (empty($products)): ?> <p>Danh muc nay chua co san pham.</p> <?php endif; ?> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== 3. FILE product.php (chi tiết sản phẩm) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $slug = $_GET['slug'] ?? ''; $stmt = $pdo->prepare( 'SELECT p.*, c.name AS category_name, c.slug AS category_slug FROM products p JOIN categories c ON c.id = p.category_id WHERE p.slug = ? AND p.is_active = 1 LIMIT 1' ); $stmt->execute([$slug]); $product = $stmt->fetch(); if (!$product) { http_response_code(404); die('Khong tim thay san pham.'); } $page_title = $product['name']; $final_price = $product['sale_price'] ?: $product['price']; require __DIR__ . '/includes/header.php'; ?> <p><a href="category.php?slug=<?= e($product['category_slug']) ?>">&laquo; <?= e($product['category_name']) ?></a></p> <div class="product-detail" style="display:flex; gap:30px; flex-wrap:wrap; background:#fff; padding:20px; border-radius:8px; margin-top:14px;"> <div style="flex:1; min-width:280px;"> <img src="assets/uploads/products/<?= e($product['image'] ?: 'no-image.png') ?>" alt="<?= e($product['name']) ?>" style="width:100%; border-radius:6px;"> </div> <div style="flex:1; min-width:280px;"> <h1 class="page-title"><?= e($product['name']) ?></h1> <p style="margin:10px 0;"> <span class="price" style="font-size:24px;"><?= format_price($final_price) ?></span> <?php if ($product['sale_price']): ?> <span class="price-old" style="font-size:16px;"><?= format_price($product['price']) ?></span> <?php endif; ?> </p> <p>Con hang: <?= (int) $product['stock'] ?> san pham</p> <p style="margin:14px 0; color:#555;"><?= nl2br(e($product['description'])) ?></p> <?php if ($product['stock'] > 0): ?> <form action="cart_add.php" method="post" style="display:flex; gap:10px; align-items:center;"> <?= csrf_field() ?> <input type="hidden" name="product_id" value="<?= (int) $product['id'] ?>"> <input type="number" name="quantity" value="1" min="1" max="<?= (int) $product['stock'] ?>" style="width:70px; padding:8px;"> <button type="submit" class="btn">Them vao gio hang</button> </form> <?php else: ?> <p style="color:#e53935; font-weight:bold;">San pham tam het hang</p> <?php endif; ?> </div> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== 4. FILE search.php (tìm kiếm sản phẩm) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $q = trim($_GET['q'] ?? ''); $page_title = 'Ket qua tim kiem: ' . $q; $products = []; if ($q !== '') { $stmt = $pdo->prepare( 'SELECT id, name, slug, price, sale_price, image FROM products WHERE is_active = 1 AND name LIKE :q ORDER BY created_at DESC' ); $stmt->execute([':q' => '%' . $q . '%']); $products = $stmt->fetchAll(); } require __DIR__ . '/includes/header.php'; ?> <h1 class="page-title">Ket qua tim kiem cho "<?= e($q) ?>"</h1> <div class="product-grid"> <?php foreach ($products as $p): ?> <a href="product.php?slug=<?= e($p['slug']) ?>" class="product-card"> <img src="assets/uploads/products/<?= e($p['image'] ?: 'no-image.png') ?>" alt="<?= e($p['name']) ?>"> <div class="info"> <div class="name"><?= e($p['name']) ?></div> <span class="price"><?= format_price($p['sale_price'] ?: $p['price']) ?></span> </div> </a> <?php endforeach; ?> <?php if (empty($products)): ?> <p>Khong tim thay san pham phu hop.</p> <?php endif; ?> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== GHI CHÚ QUAN TRỌNG VỀ BẢO MẬT (áp dụng ngay từ bây giờ) ============================== - Mọi câu SQL đều dùng $pdo->prepare() + execute([...]) (prepared statement), tuyệt đối không nối chuỗi kiểu "WHERE slug = '$slug'" - đó là lỗ hổng SQL Injection kinh điển. - Mọi dữ liệu từ CSDL khi in ra HTML đều bọc qua e() (trừ trường hợp cố ý cho phép xuống dòng như mô tả sản phẩm thì dùng nl2br(e(...)) - vẫn escape trước rồi mới nl2br, thứ tự này không được đảo ngược). - File "no-image.png" các bạn tự tìm 1 ảnh placeholder bất kỳ (kích thước vuông, VD 400x400) bỏ vào assets/uploads/products/no-image.png để không bị lỗi ảnh vỡ khi sản phẩm chưa có ảnh riêng. Phần 5 tiếp theo: xây dựng giỏ hàng hoàn chỉnh bằng session (thêm, sửa số lượng, xoá sản phẩm khỏi giỏ, tính tổng tiền). Hẹn gặp lại anh em! --- Hết Phần 4 --- ---------------------------------------- MỤC LỤC LOẠT BÀI: 1. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 1: Tổng Quan Dự Án, Yêu Cầu Môi Trường & Cấu Trúc Thư Mục - https://diendan.anyvlogs.info/chu-de/457-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-1-tong-quan-du-an-yeu-cau-moi-truong-cau-truc-thu-muc 2. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 2: Thiết Kế Cơ Sở Dữ Liệu (Database) Đầy Đủ Cho Website Bán Hàng - https://diendan.anyvlogs.info/chu-de/458-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-2-thiet-ke-co-so-du-lieu-database-day-du-cho-website-ban-hang 3. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 3: Dựng Khung Ứng Dụng - Kết Nối PDO, Hàm Dùng Chung, Giao Diện Chung - https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung >> 4. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 4: Trang Chủ, Danh Mục Sản Phẩm & Chi Tiết Sản Phẩm - https://diendan.anyvlogs.info/chu-de/460-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-4-trang-chu-danh-muc-san-pham-chi-tiet-san-pham 5. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 5: Xây Dựng Giỏ Hàng (Cart) Bằng Session - https://diendan.anyvlogs.info/chu-de/461-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-5-xay-dung-gio-hang-cart-bang-session 6. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 6: Đăng Ký, Đăng Nhập, Đăng Xuất Thành Viên - https://diendan.anyvlogs.info/chu-de/462-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-6-dang-ky-dang-nhap-dang-xuat-thanh-vien 7. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 7: Đặt Hàng, Thanh Toán COD & Lịch Sử Đơn Hàng - https://diendan.anyvlogs.info/chu-de/463-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-7-dat-hang-thanh-toan-cod-lich-su-don-hang 8. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 8: Trang Quản Trị (Admin) - Quản Lý Danh Mục, Sản Phẩm, Đơn Hàng - https://diendan.anyvlogs.info/chu-de/464-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-8-trang-quan-tri-admin-quan-ly-danh-muc-san-pham-don-hang 9. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 9: Bảo Mật Website (CSRF, XSS, SQL Injection, Upload An Toàn) - https://diendan.anyvlogs.info/chu-de/465-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-9-bao-mat-website-csrf-xss-sql-injection-upload-an-toan 10. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 10 (Kết Thúc): Tối Ưu Hiệu Năng & Triển Khai Lên Hosting Thật - https://diendan.anyvlogs.info/chu-de/466-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-10-ket-thuc-toi-uu-hieu-nang-trien-khai-len-hosting-that Phần trước: https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung Phần sau: https://diendan.anyvlogs.info/chu-de/461-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-5-xay-dung-gio-hang-cart-bang-session
0 Facebook
2. 19:48 14/10/2026
Trả lời Quản Trị Viên Tiếp Phần 3 (đã có bộ khung + giao diện chung). Hôm nay bắt đầu hiển thị dữ liệu thật: trang chủ, trang danh mục, trang …
Đang theo dõi series từ phần 1, code mẫu rất rõ ràng và dễ áp dụng thực tế. Cách tách logic xử lý dữ liệu ra khỏi view giúp bảo trì sau này nhẹ nhàng hơn nhiều so với nhét hết vào file index. Một lưu ý nhỏ: ở phần phân trang danh mục, nếu dùng `LIMIT` kết hợp `OFFSET` thì khi data lớn (vài chục nghìn record) sẽ bị chậm dần. Nên cân nhắc dùng keyset pagination (WHERE id > last_id) cho mượt hơn, hoặc thêm index composite cho cột lọc + sắp xếp. Anh/admin có dự định đưa phần tìm kiếm nâng cao (lọc theo khoảng giá, thuộc tính, sắp xếp) vào phần sau không? Phần này thường đau đầu nhất khi phải xây dựng query động an toàn khỏi SQL injection.
0
Vui lòng đăng nhập để tham gia trả lời chủ đề này.
Điều hướng nhanh

Bạn muốn khám phá điều gì?

Tìm chủ đề, bài viết hoặc đi thẳng đến một khu vực của cộng đồng.

Esc để đóng Ctrl K để mở lại