Trang này chứa các hướng dẫn chi tiết để tạo website và các bước cấu hình pages cho team code làm theo.
0. Cách tạo website
Quy trình thực tế để tạo website cho team code:
đăng nhập hệ thống, tạo website mới, nhập domain,
hệ thống tự tạo database theo domain, sau đó tải code về và cấu hình pages.
Bước 1: Đăng nhập hệ thống
Đăng nhập vào website quản trị/dashboard bằng tài khoản được cấp.
Kiểm tra tài khoản có quyền tạo website mới.
Bước 2: Thêm mới website
Vào menu: Quản lý hệ thống > Quản lý website.
Trong màn hình này có 2 chức năng theo nhu cầu người dùng:
Thêm mới web: dùng khi tạo website mới hoàn toàn.
Copy website: dùng khi cần nhân bản từ một web đã có.
Nhập domain/subdomain theo yêu cầu dự án rồi thực hiện chức năng tương ứng.
Bước 4: Tải code website về
Sau khi website được tạo thành công, bấm tải code từ thư mục Views trong source.
Đường dẫn thư mục views: /var/www/songtien.hdcode.name.vn/module/Frontend/Views.
1. Quản lý pages blocks
Tạo pages blocks cho website ngay sau khi tạo website xong:
Vào admin Pages của website đó.
Chọn page cần cấu hình và vào phần Pages Blocks.
Thêm block theo giao diện yêu cầu, sắp xếp bằng sort.
Lưu lại và kiểm tra hiển thị frontend ngay sau khi chỉnh.
2. Cài đặt website
Dưới đây là các biến và code cần thiết để cài đặt website:
Các thuộc tính của cài đặt website
Thuộc tính
Kiểu dữ liệu
Mô tả
id
int(11)
ID của cài đặt
name
varchar(255)
Tên cài đặt. Sử dụng: <?php echo $configByWebsite['name'];?>
data
text
Dữ liệu cài đặt dạng text (đã được decode JSON thành array trong $configByWebsite). Chỉ cần sử dụng: <?php echo $configByWebsite['tên_thuộc_tính'];?> (tên_thuộc_tính chính là tên không dấu)
created_at
timestamp
Thời gian tạo (định dạng: Y-m-d H:i:s)
updated_at
timestamp
Thời gian cập nhật (định dạng: Y-m-d H:i:s)
user_insert
int(11)
ID người tạo
user_update
int(11)
ID người cập nhật
data_code
text
Dữ liệu code bổ sung dạng text (chỉ dùng trong admin, không sử dụng ngoài frontend)
js_header
text
Code JavaScript chèn vào phần header (đã được lấy ra trong meta.php). Sử dụng: <?php echo $configByWebsite['js_header'];?>
js_footer
text
Code JavaScript chèn vào phần footer (đã được lấy ra trong meta.php). Sử dụng: <?php echo $configByWebsite['js_footer'];?>
<?php
include VIEWS.'/default_fmax_website/layout/meta.php';
?>
Link css js
<?php echo URL_ASSETS_WEBSITE ?>
3. Lấy danh sách sản phẩm tin tức
Code để lấy items theo điều kiện:
Các thuộc tính của items
Thuộc tính
Kiểu dữ liệu
Mô tả
id
int(11)
ID của item
name
varchar(255)
Tên item
slug
varchar(255)
Đường dẫn URL của item
images
varchar(255)
Hình ảnh chính của item (size: 300x300 - width x height). Sử dụng: <?php echo $this->getImageUrl($val['images'], '300x300');?> (không ghi size sẽ lấy hình gốc)
gallery_json
text
Thư viện ảnh (size: text). Trả về array chứa danh sách tên hình. Sử dụng: <?php foreach($val['gallery_json'] as $img) { echo $this->getImageUrl($img, '300x300'); }?> (không ghi size sẽ lấy hình gốc)
description
text
Mô tả ngắn của item
detail
longtext
Chi tiết đầy đủ của item
meta_title
varchar(255)
Tiêu đề SEO
meta_description
varchar(300)
Mô tả SEO
meta_keyword
varchar(300)
Từ khóa SEO
fb_title
varchar(255)
Tiêu đề Facebook
fb_description
varchar(300)
Mô tả Facebook
fb_image
varchar(255)
Hình ảnh Facebook
category_id
int(11)
ID danh mục chứa item
user_insert
int(11)
ID người tạo
user_update
int(11)
ID người cập nhật
id_edit
int(11)
ID chỉnh sửa
status
tinyint(1)
Trạng thái (0/1)
created_at
timestamp
Thời gian tạo
updated_at
timestamp
Thời gian cập nhật
p
int(11)
Phân loại (dùng để lọc items. Ví dụ: if($val['p'] == 2)). Giá trị p chính là tham số sau URL trong admin, ví dụ: https://hdwebs.vn/admin/items/index?p=15
price
int(11)
Giá gốc
price_promotion
int(11)
Giá khuyến mãi
checkbox1
tinyint(1)
Checkbox 1 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox1'] == 1))
checkbox2
tinyint(1)
Checkbox 2 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox2'] == 1))
checkbox3
tinyint(1)
Checkbox 3 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox3'] == 1))
checkbox4
tinyint(1)
Checkbox 4 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox4'] == 1))
sort
int(11)
Thứ tự sắp xếp
data
text
Dữ liệu bổ sung dạng text
multi_detail
longtext
Chi tiết đa dạng (JSON → array). Trên trang chi tiết, mỗi phần tử thường là HTML hoàn chỉnh: <?php foreach(($detail['multi_detail'] ?? []) as $item) { echo $item; }?> — xem mục 3.1. Không tách $item['title'] / $item['content'] nếu dữ liệu đã là chuỗi HTML.
<?php foreach($list_items as $val) {
if($val['p'] == 2) {?>
// Code hiển thị item ở đây
<?php }
}?>
Lấy items kết hợp nhiều điều kiện (p = 2 và checkbox1 = 1)
<?php
// Lấy items có p = 2 VÀ checkbox1 = 1
foreach($list_items as $val) {
if($val['p'] == 2 && $val['checkbox1'] == 1) {?>
<div>
<h3><?php echo $val['name'];?></h3>
<!-- Code hiển thị item ở đây -->
</div>
<?php }
}
?>
Danh sách có phân trang (lọc p = 2)
Giải thích code:
Bước 1 - Lọc dữ liệu: Tạo mảng tạm $list_items_temp và lọc các item có p == 2 từ mảng $list_items. Lưu ý: Giá trị p chính là tham số sau URL trong admin, ví dụ: https://hdwebs.vn/admin/items/index?p=15 (trong ví dụ này dùng p = 2)
Bước 2 - Thiết lập phân trang: Tạo mảng $option với các tham số:
'data': Dữ liệu đã lọc để phân trang
'baseUrl': URL cơ sở (slug của trang) để tạo link phân trang
'perPage': Số item hiển thị trên mỗi trang (20 items/trang)
'currentPage': Trang hiện tại, lấy từ $_GET['pages'] hoặc mặc định là 1
Bước 3 - Gọi hàm phân trang: Sử dụng $this->paginationFrontend($option) để tạo phân trang, kết quả trả về trong $list
Bước 4 - Hiển thị danh sách: Dùng vòng lặp foreach để duyệt qua $list['items'] và hiển thị từng item
Bước 5 - Hiển thị phân trang: In ra $list['pagination'] để hiển thị các nút chuyển trang
<?php
// Bước 1: Tạo mảng tạm để chứa các item đã lọc
$list_items_temp = array();
// Bước 2: Duyệt qua tất cả các item trong $list_items và lọc những item có p = 2
// Lưu ý: Giá trị p chính là tham số sau URL trong admin, ví dụ: https://hdwebs.vn/admin/items/index?p=15
foreach ($list_items as $val){
if($val['p'] == 2){
// Thêm item vào mảng tạm nếu thỏa điều kiện
$list_items_temp[] = $val;
}
}
// Bước 3: Thiết lập các tham số cho phân trang
$option = array(
'data' => $list_items_temp, // Dữ liệu đã lọc để phân trang
'baseUrl' => '/' . $pages['slug'], // URL cơ sở (slug của trang) để tạo link phân trang
'perPage' => 20, // Số item hiển thị trên mỗi trang (20 items/trang)
'currentPage' => $_GET['pages'] ?? 1 // Trang hiện tại, lấy từ $_GET['pages'] hoặc mặc định là 1
);
// Bước 4: Gọi hàm phân trang và lưu kết quả vào $list
$list = $this->paginationFrontend($option);
?>
<?php
// Bước 5: Duyệt qua các item đã được phân trang và hiển thị
foreach ($list['items'] as $val){?>
<!-- Code hiển thị item ở đây, ví dụ: -->
<div>
<!-- <?php echo $val['title'];?> -->
<!-- <?php echo $val['content'];?> -->
</div>
<?php }?>
<?php
// Bước 6: Hiển thị các nút phân trang (Previous, Next, số trang...)
echo $list['pagination'];
?>
3.1. Chi tiết sản phẩm tin tức
Code để lấy và hiển thị chi tiết sản phẩm/tin tức:
Lấy chi tiết sản phẩm/tin tức
Lưu ý về multi_detail:
Trong phần Chi tiết đa dạng, biến $item đã chứa toàn bộ HTML của phần chi tiết (bao gồm cả title, content, và các thẻ HTML khác). Do đó, chỉ cần echo $item trực tiếp, không cần echo từng phần như $item['title'] hay $item['content'].
<?php if(!empty($detail)) {?>
<div>
<!-- Tên sản phẩm/tin tức -->
<h1><?php echo $detail['name'];?></h1>
<!-- Hình ảnh chính (size: 800x600) -->
<img src="<?php echo $this->getImageUrl($detail['images'], '800x600');?>" alt="<?php echo $detail['name'];?>">
<!-- Mô tả ngắn -->
<p><?php echo $detail['description'];?></p>
<!-- Chi tiết đầy đủ -->
<div><?php echo $detail['detail'];?></div>
<!-- Thư viện ảnh: Duyệt qua mảng gallery_json (mỗi $img là tên file ảnh) -->
<?php if(!empty($detail['gallery_json'])) { ?>
<?php foreach($detail['gallery_json'] as $img) { ?>
<img src="<?php echo $this->getImageUrl($img, '800x600');?>" alt="">
<?php } ?>
<?php } ?>
<!-- Chi tiết đa dạng: $item đã chứa toàn bộ HTML, chỉ cần echo trực tiếp (không cần echo $item['title'] hay $item['content']) -->
<?php if(!empty($detail['multi_detail'])) { ?>
<?php foreach($detail['multi_detail'] as $item) { ?>
<?php echo $item;?>
<?php } ?>
<?php } ?>
<!-- Giá sản phẩm (nếu có) -->
<?php if(!empty($detail['price'])) { ?>
<p>Giá: <?php echo number_format($detail['price']);?> đ</p>
<!-- Giá khuyến mãi (nếu có) -->
<?php if($detail['price_promotion'] > 0) { ?>
<p>Giá KM: <?php echo number_format($detail['price_promotion']);?> đ</p>
<?php } ?>
<?php } ?>
</div>
<?php }?>
4. Lấy danh mục sản phẩm tin tức
Code để lấy danh mục theo điều kiện:
Các thuộc tính của danh mục
Thuộc tính
Kiểu dữ liệu
Mô tả
id
int(11)
ID của danh mục
name
varchar(255)
Tên danh mục
slug
varchar(255)
Đường dẫn URL của danh mục. Sử dụng: <?php echo '/' . $val['slug'];?>
parent_id
int(11)
ID danh mục cha (0 nếu là danh mục gốc)
icon
varchar(255)
Icon của danh mục (tên class icon hoặc đường dẫn icon)
images
varchar(255)
Hình ảnh danh mục (size: 300x300 - width x height). Sử dụng: <?php echo $this->getImageUrl($val['images'], '300x300');?> (không ghi size sẽ lấy hình gốc)
description
text
Mô tả ngắn của danh mục
meta_title
varchar(255)
Tiêu đề SEO (dùng cho thẻ <title>)
meta_description
varchar(300)
Mô tả SEO (dùng cho thẻ meta description)
meta_keyword
varchar(300)
Từ khóa SEO (dùng cho thẻ meta keywords)
fb_title
varchar(255)
Tiêu đề Facebook (dùng cho Open Graph)
fb_description
varchar(300)
Mô tả Facebook (dùng cho Open Graph)
fb_image
varchar(255)
Hình ảnh Facebook (size: 300x300 - width x height). Sử dụng: <?php echo $this->getImageUrl($val['fb_image'], '300x300');?> (không ghi size sẽ lấy hình gốc)
detail
text
Chi tiết đầy đủ của danh mục
user_insert
int(11)
ID người tạo
user_update
int(11)
ID người cập nhật
status
tinyint(1)
Trạng thái (0 = ẩn, 1 = hiển thị)
created_at
timestamp
Thời gian tạo (định dạng: Y-m-d H:i:s)
updated_at
timestamp
Thời gian cập nhật (định dạng: Y-m-d H:i:s)
sort
int(11)
Thứ tự sắp xếp (số càng nhỏ càng hiển thị trước)
p
int(11)
Phân loại (dùng để lọc danh mục. Ví dụ: if($val['p'] == 13)). Giá trị p chính là tham số sau URL trong admin danh mục, ví dụ: https://hdwebs.vn/admin/category/index?p=13
checkbox1
tinyint(1)
Checkbox 1 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox1'] == 1) để lấy danh mục hiển thị ngoài trang chủ)
checkbox2
tinyint(1)
Checkbox 2 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox2'] == 1))
checkbox3
tinyint(1)
Checkbox 3 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox3'] == 1))
checkbox4
tinyint(1)
Checkbox 4 (giá trị 0 hoặc 1, dùng làm điều kiện lọc. Ví dụ: if($val['checkbox4'] == 1))
Lấy danh mục hiển thị ngoài trang chủ (checkbox1 = 1)
<?php
// Lấy danh mục có checkbox1 = 1 để hiển thị ngoài trang chủ
foreach($list_cat as $val) {
if($val['checkbox1'] == 1) {?>
<div>
<h3><?php echo $val['name'];?></h3>
<img src="<?php echo $this->getImageUrl($val['images'], '300x300');?>" alt="<?php echo $val['name'];?>">
<p><?php echo $val['description'];?></p>
<a href="<?php echo '/' . $val['slug'];?>">Xem chi tiết</a>
</div>
<?php }
}
?>
Lấy danh mục kết hợp nhiều điều kiện (p = 13 và checkbox1 = 1)
<?php
// Lấy danh mục có p = 13 VÀ checkbox1 = 1
foreach($list_cat as $val) {
if($val['p'] == 13 && $val['checkbox1'] == 1) {?>
<div>
<h3><?php echo $val['name'];?></h3>
<!-- Code hiển thị danh mục ở đây -->
</div>
<?php }
}
?>
Dữ liệu block dạng text (đã được decode JSON thành array). Chỉ cần sử dụng: <?php echo $blocks_55['tên_thuộc_tính'];?> (tên_thuộc_tính chính là tên không dấu)
id_blocks
int(11)
ID loại block. Tên biến sử dụng: $blocks_55, $blocks_56, $blocks_57 (số 55, 56, 57 chính là giá trị id_blocks). Lưu ý: Tên biến này có thể copy trong admin
images
varchar(255)
Hình ảnh block (chỉ lấy ra trong admin, không sử dụng ngoài frontend)
Lấy danh sách block
<?php foreach ($blocks_55['list'] as $key => $val){?>
// Code hiển thị block ở đây
<?php }?>
Các ví dụ sử dụng block
Giải thích:
$blocks_55, $blocks_56, $blocks_57: Tên biến block (số 55, 56, 57 là ID loại block - id_blocks). Lưu ý: Tên biến này có thể copy trong admin
Block có 2 kiểu:
['list']: Kiểu danh sách - Duyệt qua nhiều item trong block (ví dụ: $blocks_55['list']). Sử dụng vòng lặp foreach để duyệt qua từng item
['detail']: Kiểu chi tiết - Chỉ có 1 item (ví dụ: $blocks_56['detail']). Lấy trực tiếp không cần vòng lặp
Ví dụ kiểu list (danh sách):
<?php foreach ($blocks_55['list'] as $key => $val){?>
<!-- Tên hình -->
<?php echo $val['multi_images'][0]['name'][0]; ?>
<!-- Lấy hình (size: 1600x420) -->
<img src="<?php echo $this->getImageUrl($val['multi_images'][0]['imagesPC'], '1600x420'); ?>" alt="">
<!-- Text items -->
<?php echo $val['text'][0];?>
<?php }?>
Ví dụ kiểu detail (chi tiết):
<?php
// Chi tiết items (block detail)
echo $blocks_56['detail']['detail'];
?>
<?php
// Hình ảnh từ block detail (size: 560x430)
echo $this->getImageUrl($blocks_56['detail']['multi_images'][0]['imagesPC'], '560x430');
?>
<?php
// Block detail text
echo $blocks_57['detail']['text'][0];
?>
6. Dữ liệu contact form
Code để lấy và hiển thị dữ liệu contact form:
Các thuộc tính của contact form
Thuộc tính
Kiểu dữ liệu
Mô tả
id
int(11)
ID của contact form
name
varchar(100)
Tên contact form
data
text
Dữ liệu bổ sung dạng text (đã được decode JSON thành array). Sử dụng: <?php echo $val['data']['tên sản phẩm'];?> (tên sản phẩm là tên trường động ngoài các column trong bảng, đặt tên theo định dạng data['tên trường'])
fullname
varchar(255)
Họ và tên người gửi
email
varchar(255)
Email người gửi
phone
varchar(255)
Số điện thoại
tieu_de
varchar(255)
Tiêu đề
dia_chi
varchar(255)
Địa chỉ
noi_dung
text
Nội dung tin nhắn
p
int(11)
Phân loại (dùng để lọc contact form. Ví dụ: if($val['p'] == 3)). Giá trị p chính là tham số sau URL trong admin
status
int(11)
Trạng thái (0 = chưa đọc, 1 = đã đọc)
notes
text
Ghi chú
user_update
int(11)
ID người cập nhật
count_request
int(11)
Số lần yêu cầu
created_at
timestamp
Thời gian gửi (định dạng: Y-m-d H:i:s)
updated_at
timestamp
Thời gian cập nhật (định dạng: Y-m-d H:i:s)
Lưu ý:
Ngoài các thuộc tính cố định: fullname, email, phone, tieu_de, dia_chi, noi_dung, các thuộc tính khác sẽ được đặt tên theo định dạng data['tên text'] (ví dụ: data['tên sản phẩm'], data['số lượng']). Các trường này sẽ được lưu vào cột data dưới dạng JSON.
Code form contact
<form id="contactForm" method="POST">
<input type="hidden" name="p" value="3">
<div class="form-group">
<label for="fullname">Họ và tên *</label>
<input type="text" id="fullname" name="fullname" required placeholder="Nhập họ và tên của bạn">
</div>
<div class="form-group">
<label for="phone">Số điện thoại *</label>
<input type="tel" id="phone" name="phone" required placeholder="Nhập số điện thoại">
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required placeholder="Nhập email của bạn">
</div>
<div class="form-group">
<label for="tieu_de">Dịch vụ</label>
<select id="tieu_de" name="tieu_de">
<option value="">-- Chọn dịch vụ --</option>
<?php if (!empty($data['list_items']) && is_array($data['list_items'])): ?>
<?php foreach ($data['list_items'] as $item): ?>
<?php if (isset($item['p']) && $item['p'] == 23): ?>
<option value="<?php echo !empty($item['name']) ? htmlspecialchars($item['name']) : ''; ?>">
<?php echo !empty($item['name']) ? htmlspecialchars($item['name']) : ''; ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<div class="form-group">
<label for="dia_chi">Địa chỉ</label>
<input type="text" id="dia_chi" name="dia_chi" placeholder="Nhập địa chỉ">
</div>
<div class="form-group">
<label for="noi_dung">Nội dung tin nhắn *</label>
<textarea id="noi_dung" name="noi_dung" required placeholder="Nhập nội dung tin nhắn của bạn"></textarea>
</div>
<!-- Các trường động ngoài column, sử dụng data['tên trường'] -->
<div class="form-group">
<label for="ten_san_pham">Tên sản phẩm</label>
<input type="text" id="ten_san_pham" name="data[tên sản phẩm]" placeholder="Nhập tên sản phẩm">
</div>
<button type="submit" class="btn-submit">Gửi tin nhắn</button>
</form>
Code JavaScript gửi form
<script>
// Hàm gửi contact form
function handleSendContact(formId) {
const contactForm = document.getElementById(formId || 'contactForm');
if (!contactForm) {
alert('Không tìm thấy form!');
return;
}
// Get form data
const formData = new FormData(contactForm);
const fullname = formData.get('fullname');
const phone = formData.get('phone');
const email = formData.get('email');
const noi_dung = formData.get('noi_dung');
// Basic validation
if (!fullname || !phone || !email || !noi_dung) {
alert('Vui lòng điền đầy đủ các trường bắt buộc (*)');
return;
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('Vui lòng nhập email hợp lệ');
return;
}
// Phone validation (Vietnamese phone number)
const phoneRegex = /^(0|\+84)[0-9]{9,10}$/;
if (!phoneRegex.test(phone.replace(/\s/g, ''))) {
alert('Vui lòng nhập số điện thoại hợp lệ');
return;
}
// Disable submit button
const submitBtn = contactForm.querySelector('.btn-submit');
const originalText = submitBtn ? submitBtn.textContent : '';
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = 'Đang gửi...';
}
// Send form data via AJAX
fetch('/frontend/ajaxs/handleSendContact', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(res => {
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.json();
})
.then(response => {
if (response.success) {
alert(response.message || 'Cảm ơn bạn đã liên hệ! Chúng tôi sẽ phản hồi sớm nhất có thể.');
contactForm.reset();
} else {
alert(response.message || 'Có lỗi xảy ra khi gửi liên hệ. Vui lòng thử lại sau.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Có lỗi xảy ra khi gửi liên hệ. Vui lòng thử lại sau.');
})
.finally(() => {
// Restore submit button
if (submitBtn) {
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
});
}
// Sử dụng với form submit
document.addEventListener("DOMContentLoaded", function() {
const contactForm = document.getElementById('contactForm');
if (contactForm) {
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
handleSendContact('contactForm');
});
}
});
</script>
<!-- Hoặc sử dụng trực tiếp trong button -->
<!-- <button type="button" class="btn-submit" onclick="handleSendContact('contactForm')">Gửi tin nhắn</button> -->
7. Ghép code chi tiết (admin — Xử lý code)
Phần này dành cho team dùng tài khoản code trong admin: vào Code web (hoặc luồng tương đương), chọn website, mở Xử lý code / tạo block cho page, dán HTML mẫu và để hệ thống ghép biến $detail, ảnh, giá, breadcrumb.
Class gốc và biến
HTML template thường có class gốc dạng hd-webs-vn-{id} (theo page/block trong admin).
Trang chi tiết sản phẩm/tin: dùng $detail (tên, slug, ảnh, giá, category_id, …), $list_cat (danh mục), hằng URL, URL_ASSETS_WEBSITE, và $list_items khi page cần danh sách.
Breadcrumb tự động (chi tiết)
Nếu trong HTML có phần tử mà class hoặc id chứa chữ breadcrumb (không phân biệt hoa thường), khi ghép code chi tiết hệ thống sẽ thay toàn bộ nội dung bên trong bằng đoạn PHP: Trang chủ (link theo URL) → tên danh mục (link: khớp $detail['category_id'] với $list_cat) → tên sản phẩm (link theo $detail['slug']). Giữ nguyên thẻ bọc (ví dụ <nav>, <div>) — chỉ nội dung bên trong được thay.
Gợi ý gallery (ảnh phụ)
Thư viện ảnh nằm ở $detail['gallery_json'] (mảng tên file). Có thể dùng một vòng lặp cho ảnh lớn và một vòng lặp cho dải thumbnail, đồng bộ chỉ số (cùng một mảng nguồn) — phù hợp slider kiểu gallery chính + strip ảnh nhỏ.