This commit is contained in:
2026-03-10 15:07:29 +07:00
parent e2189983f6
commit 1f599a859b
52 changed files with 4136 additions and 1811 deletions

View File

@@ -0,0 +1,103 @@
<script>
// ── Helpers ──────────────────────────────────────────────────────────────
function formatPrice(n) {
return Number(n).toLocaleString('vi-VN') + '₫';
}
// cartItems cần global để các onclick inline gọi được
var cartItems = {};
// ── Update total ─────────────────────────────────────────────────────────
function updateCartTotal() {
var total = 0;
Object.values(cartItems).forEach(function (item) {
if (item.price > 0) total += item.price * item.qty;
});
var totalEl = document.getElementById('cart-total');
if (totalEl) totalEl.textContent = formatPrice(total);
}
// ── Quantity controls ─────────────────────────────────────────────────────
function cartInc(id) {
var item = cartItems[id];
if (!item) return;
item.qty++;
item.el.dataset.qty = item.qty;
item.el.querySelector('.item-qty').textContent = item.qty;
if (item.price > 0) {
item.el.querySelector('.item-total-price').textContent = formatPrice(item.price * item.qty);
}
updateCartTotal();
}
function cartDec(id) {
var item = cartItems[id];
if (!item || item.qty <= 1) return;
item.qty--;
item.el.dataset.qty = item.qty;
item.el.querySelector('.item-qty').textContent = item.qty;
if (item.price > 0) {
item.el.querySelector('.item-total-price').textContent = formatPrice(item.price * item.qty);
}
updateCartTotal();
}
// ── Remove item ──────────────────────────────────────────────────────────
function cartRemove(id) {
var item = cartItems[id];
if (!item) return;
if (!confirm('Bạn có chắc muốn xóa sản phẩm này khỏi giỏ hàng?')) return;
item.el.closest('.cart-item-wrap').remove();
delete cartItems[id];
updateCartTotal();
}
// ── Delivery tab switch ───────────────────────────────────────────────────
function switchTab(tab) {
var tabDelivery = document.getElementById('tab-delivery');
var tabPickup = document.getElementById('tab-pickup');
if (tab === 'delivery') {
tabDelivery.className = 'h-[46px] w-[392px] text-[14px] font-medium bg-[#f3f4f6] border-b-2 border-[#e7000b] text-[#e7000b] rounded-tl-lg rounded-tr-lg';
tabPickup.className = 'h-[46px] w-[408px] text-[14px] font-medium text-[#4a5565] border-b-2 border-transparent';
} else {
tabPickup.className = 'h-[46px] w-[408px] text-[14px] font-medium bg-[#f3f4f6] border-b-2 border-[#e7000b] text-[#e7000b] rounded-tl-lg rounded-tr-lg';
tabDelivery.className = 'h-[46px] w-[392px] text-[14px] font-medium text-[#4a5565] border-b-2 border-transparent';
}
}
// ── Init sau khi DOM sẵn sàng ────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', function () {
// Build cart state từ DOM
document.querySelectorAll('.cart-item').forEach(function (el) {
var id = el.dataset.id;
var price = parseInt(el.dataset.price) || 0;
var qty = parseInt(el.dataset.qty) || 1;
cartItems[id] = { el: el, price: price, qty: qty };
});
// Format giá từng item (số thô → "1.850.000₫")
document.querySelectorAll('.cart-item').forEach(function (el) {
var id = el.dataset.id;
var item = cartItems[id];
if (!item) return;
if (item.price > 0) {
var priceEl = el.querySelector('.item-total-price');
if (priceEl) priceEl.textContent = formatPrice(item.price * item.qty);
}
var marketEl = el.querySelector('.item-market-price');
if (marketEl) {
var raw = parseInt(marketEl.textContent) || 0;
if (raw > 0) marketEl.textContent = formatPrice(raw);
}
});
// Format tổng tiền
var totalEl = document.getElementById('cart-total');
if (totalEl) {
var raw = parseInt(totalEl.textContent.replace(/\D/g, '')) || 0;
totalEl.textContent = formatPrice(raw);
}
});
</script>

View File

@@ -1,83 +0,0 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monaco-editor@0.34.1/min/vs/editor/editor.main.css">
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.34.1/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.34.1/min/vs' } });
require(['vs/editor/editor.main'], function () {
monaco.editor.defineTheme('myCustomTheme', {
base: 'vs',
inherit: true,
rules: [
{ token: 'tag', foreground: '4CAF50', fontStyle: 'bold' }, // Màu cam cho thẻ HTML
{ token: 'attribute.name', foreground: 'e00000' }, // Màu xanh lá cho thuộc tính
{ token: 'attribute.value', foreground: 'e00000' }, // Màu vàng cho giá trị thuộc tính
{ token: 'string', foreground: 'e00000' }, // Màu xanh dương cho chuỗi
{ token: 'comment', foreground: '#4CAF50', fontStyle: 'italic' },
],
colors: {
'editor.foreground': '#000000',
'editorGutter.background': '#f6f6f6',
'editor.lineNumber.foreground': '#000000',
}
})
monaco.editor.create(document.getElementById('tpl_editor'), {
value: `<section class= "section-breakcrumb routing py-12 line-clamp" >
<div class="global-breadcrumb container">
<ol itemscope="" itemtype="http://schema.org/BreadcrumbList" class="list-style-none
d- flex">
<li class="routing-link" itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a href="/" itemprop="item" class="nopad-l">
<span itemprop="name">Trang chủ</span>
</a>
<meta itemprop="position" content="1">
</li>
<li class="routing-link" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
<a href="{{ path.url }}" itemprop="item" class="nopad-l">
<span itemprop="name"> {{ path.name }} </span>
</a>
<meta itemprop="position" content="{{ counter }}" />
</li>
</ol>
<ol itemscope="" itemtype="http://schema.org/BreadcrumbList" class="list-style-none d-flex">
<li class="routing-link" itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a href="/" itemprop="item" class="nopad-l">
<span itemprop="name">Trang chủ</span>
</a>
<meta itemprop="position" content="1">
</li>
`,
language: 'html',
theme: 'myCustomTheme',
automaticLayout: true,
minimap: { enabled: false },
autoClosingBrackets: true,
autoClosingQuotes: true,
autoIndent: true,
wordWrap: 'on',
});
});
function open_template_list(id) {
var $template = $('#template_list_' + id)
if ($template.prop('open')) {
$('#template_list_' + id).prop('open', false);
} else {
$('#template_list_' + id).prop('open', true);
}
}
function show_older_version() {
$('#older_version').toggle()
}
</script>

View File

@@ -31,8 +31,8 @@
prevEl: el.querySelector(".swiper-button-prev"),
},
breakpoints: {
1280: { slidesPerView: 4 },
1600: { slidesPerView: 5 },
1200: { slidesPerView: 5 },
1600: { slidesPerView: 6 },
},
})
})

View File

@@ -12,45 +12,12 @@
{% if global.view == 'home' %}
{% include javascript/product_list %}
{% elsif global.view == 'detail' %}
{% include javascript/product-detail %}
{% endif %}
{% include javascript/product_form %}
{% elsif global.module == 'cart' %}
{% elsif global.module == 'deal' %}
{% include javascript/cart %}
{% include javascript/product_form %}
{% elsif global.module == 'marketing' %}
{% include javascript/marketing_form %}
{% elsif global.module == 'brand' %}
{% include javascript/brand %}
{% elsif global.module == 'report' %}
{% include javascript/visitor %}
{% elsif global.module == 'system' %}
{% include javascript/system %}
{% elsif global.module == 'page' %}
{% include javascript/page %}
{% elsif global.module == 'template' and global.view == 'edit-template' %}
{% include javascript/edit_template %}
{% elsif global.module == 'tag' and global.view == 'add' %}
{% include javascript/tag %}
{% elsif global.module == 'shipping2' %}
{% include javascript/shipping2 %}
{% endif %}
{% endif %}

View File

@@ -0,0 +1,226 @@
<script>
document.addEventListener("DOMContentLoaded", function () {
// Swiper Thumbs Gallery
if (typeof Swiper !== "undefined") {
var thumbsSwiper = new Swiper(".product-thumbs-swiper", {
spaceBetween: 8,
slidesPerView: "auto",
freeMode: true,
watchSlidesProgress: true,
})
new Swiper(".product-main-swiper", {
spaceBetween: 0,
thumbs: { swiper: thumbsSwiper },
})
}
// Fancybox
if (typeof Fancybox !== "undefined") {
Fancybox.bind("[data-fancybox='product-gallery']")
}
// Tab switching
var tabBtnSpec = document.getElementById("tab-btn-spec")
var tabBtnInfo = document.getElementById("tab-btn-info")
var tabContentSpec = document.getElementById("tab-content-spec")
var tabContentInfo = document.getElementById("tab-content-info")
var descriptionContent = document.getElementById("product-description-content")
var descriptionFade = document.getElementById("product-description-fade")
var toggleDescriptionBtn = document.getElementById("btn-toggle-description")
var descriptionCollapsedHeight = 420
var isDescriptionExpanded = false
function setActiveTab(active) {
if (active === "spec") {
tabBtnSpec.className = "w-[205px] h-10 rounded-[4px] border border-[#0084ff] bg-[#edf7ff] text-[16px] text-[#0084ff] font-bold tracking-[-0.32px]"
tabBtnInfo.className = "w-[205px] h-10 rounded-[4px] border border-[#e6e6e6] bg-white text-[16px] text-black font-bold tracking-[-0.32px]"
tabContentSpec.classList.remove("hidden")
tabContentInfo.classList.add("hidden")
} else {
tabBtnInfo.className = "w-[205px] h-10 rounded-[4px] border border-[#0084ff] bg-[#edf7ff] text-[16px] text-[#0084ff] font-bold tracking-[-0.32px]"
tabBtnSpec.className = "w-[205px] h-10 rounded-[4px] border border-[#e6e6e6] bg-white text-[16px] text-black font-bold tracking-[-0.32px]"
tabContentInfo.classList.remove("hidden")
tabContentSpec.classList.add("hidden")
requestAnimationFrame(syncDescriptionToggle)
}
}
function syncDescriptionToggle() {
if (!descriptionContent || !toggleDescriptionBtn || !descriptionFade || !tabContentInfo) return
if (tabContentInfo.classList.contains("hidden")) return
var fullHeight = descriptionContent.scrollHeight
if (fullHeight <= descriptionCollapsedHeight + 8) {
descriptionContent.style.maxHeight = "none"
descriptionFade.classList.add("hidden")
toggleDescriptionBtn.classList.add("hidden")
return
}
toggleDescriptionBtn.classList.remove("hidden")
if (isDescriptionExpanded) {
descriptionContent.style.maxHeight = fullHeight + "px"
toggleDescriptionBtn.textContent = "Thu gọn"
descriptionFade.classList.add("hidden")
} else {
descriptionContent.style.maxHeight = descriptionCollapsedHeight + "px"
toggleDescriptionBtn.textContent = "Xem thêm"
descriptionFade.classList.remove("hidden")
}
}
function scrollToDescription() {
if (!tabContentInfo) return
var offsetTop = tabContentInfo.getBoundingClientRect().top + window.pageYOffset - 80
window.scrollTo({
top: offsetTop > 0 ? offsetTop : 0,
behavior: "smooth",
})
}
if (tabBtnSpec && tabBtnInfo && tabContentSpec && tabContentInfo) {
tabBtnSpec.addEventListener("click", function () { setActiveTab("spec") })
tabBtnInfo.addEventListener("click", function () { setActiveTab("info") })
}
if (toggleDescriptionBtn) {
toggleDescriptionBtn.addEventListener("click", function () {
var wasExpanded = isDescriptionExpanded
isDescriptionExpanded = !isDescriptionExpanded
syncDescriptionToggle()
if (wasExpanded) {
setTimeout(scrollToDescription, 60)
}
})
}
if (tabContentInfo && !tabContentInfo.classList.contains("hidden")) {
requestAnimationFrame(syncDescriptionToggle)
}
window.addEventListener("load", syncDescriptionToggle)
var installOptionGroup = document.getElementById("install-option-group")
var installOptions = installOptionGroup ? Array.prototype.slice.call(installOptionGroup.querySelectorAll(".js-install-option")) : []
var selectedAddonInput = document.getElementById("selected-addon-id")
function setInstallOptionActive(activeOption) {
if (!activeOption) return
installOptions.forEach(function (option) {
var isActive = option === activeOption
option.classList.toggle("is-selected", isActive)
option.classList.toggle("border-[#e4057c]", isActive)
option.classList.toggle("bg-[#fff6f6]", isActive)
option.classList.toggle("border-[#e2e2e2]", !isActive)
option.classList.toggle("bg-white", !isActive)
option.setAttribute("aria-checked", isActive ? "true" : "false")
var radio = option.querySelector(".js-install-radio")
var radioDot = option.querySelector(".js-install-radio-dot")
var selectedDot = option.querySelector(".js-install-selected-dot")
if (radio) {
radio.classList.toggle("border-[#a0045c]", isActive)
radio.classList.toggle("border-[#a9a9a9]", !isActive)
}
if (radioDot) {
radioDot.classList.toggle("hidden", !isActive)
}
if (selectedDot) {
selectedDot.classList.toggle("hidden", !isActive)
}
})
if (selectedAddonInput) {
selectedAddonInput.value = activeOption.getAttribute("data-addon-id") || ""
}
}
if (installOptions.length > 0) {
installOptions.forEach(function (option) {
option.addEventListener("click", function () {
setInstallOptionActive(option)
})
option.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault()
setInstallOptionActive(option)
}
})
})
var defaultOption = installOptionGroup.querySelector(".js-install-option.is-selected") || installOptions[0]
setInstallOptionActive(defaultOption)
}
function closeCartPopup() {
var popup = document.getElementById("cart-popup")
if (!popup) return
popup.classList.add("hidden")
popup.style.display = "none"
document.body.classList.remove("overflow-hidden")
}
function showAddToCartPopup(button) {
var popup = document.getElementById("cart-popup")
if (!popup) {
// Fallback for pages without the global popup container.
var toast = document.getElementById("cart-toast")
if (toast) {
toast.classList.remove("hidden")
setTimeout(function () { toast.classList.add("hidden") }, 3000)
}
return
}
var productName = button.getAttribute("data-product-name") || ""
var productImage = button.getAttribute("data-product-image") || ""
var productPrice = button.getAttribute("data-product-price") || "Liên hệ"
var popupImg = document.getElementById("cart-popup-img")
var popupName = document.getElementById("cart-popup-name")
var popupPrice = document.getElementById("cart-popup-price")
if (popupImg) popupImg.src = productImage
if (popupName) popupName.textContent = productName
if (popupPrice) popupPrice.textContent = productPrice
popup.classList.remove("hidden")
popup.style.display = "flex"
document.body.classList.add("overflow-hidden")
}
window.closeCartPopup = closeCartPopup
var cartPopup = document.getElementById("cart-popup")
if (cartPopup) {
cartPopup.addEventListener("click", function (event) {
if (event.target === cartPopup) {
closeCartPopup()
}
})
}
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
closeCartPopup()
}
})
var addToCartBtn = document.getElementById("btn-add-to-cart")
if (addToCartBtn) {
addToCartBtn.addEventListener("click", function () {
showAddToCartPopup(addToCartBtn)
})
}
var buyNowBtn = document.getElementById("btn-buy-now")
if (buyNowBtn) {
buyNowBtn.addEventListener("click", function () {
window.location.href = "/cart"
})
}
})
</script>