Spaces:
Running
Running
Add deep linking & share button: product URLs, hash routing fix, copy shareable link
Browse files- index.html +93 -4
index.html
CHANGED
|
@@ -313,6 +313,13 @@ textarea.form-input{height:120px;resize:vertical}
|
|
| 313 |
.cart-drawer{width:100%;max-width:100%}
|
| 314 |
}
|
| 315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
</style>
|
| 317 |
</head>
|
| 318 |
<body>
|
|
@@ -464,6 +471,9 @@ textarea.form-input{height:120px;resize:vertical}
|
|
| 464 |
</div>
|
| 465 |
</div>
|
| 466 |
|
|
|
|
|
|
|
|
|
|
| 467 |
<footer class="footer"><div class="container">
|
| 468 |
<div class="footer-grid">
|
| 469 |
<div class="footer-brand"><div class="logo-name" style="color:#fff;font-size:1.1rem">MALL<span style="color:var(--a)">OCA</span> & EURO<span style="color:var(--a)">GOLD</span> & GR<span style="color:var(--a)">OB</span></div><p>1979 sản phẩm với dữ liệu chi tiết đầy đủ — hình ảnh, thông số kỹ thuật, mô tả, tính năng — tích hợp từ malloca.com, eurogold.vn & catalogue Grob.</p>
|
|
@@ -493,7 +503,7 @@ if(p.specs){Object.entries(p.specs).forEach(([k,v])=>{parts.push(k);parts.push(S
|
|
| 493 |
return nVN(parts.join(' '));
|
| 494 |
}
|
| 495 |
|
| 496 |
-
function init(){buildCats();buildPrices();buildBrands();render()}
|
| 497 |
|
| 498 |
function buildCats(){
|
| 499 |
let cats={};D.forEach(p=>{if(!cats[p.catSlug])cats[p.catSlug]={n:p.cat,i:p.catIcon,c:0};cats[p.catSlug].c++});
|
|
@@ -558,6 +568,8 @@ document.getElementById('pag').innerHTML=h;
|
|
| 558 |
// ===== DETAIL VIEW =====
|
| 559 |
function showDetail(idx){
|
| 560 |
let p=D[idx];
|
|
|
|
|
|
|
| 561 |
document.getElementById('listView').style.display='none';
|
| 562 |
document.querySelector('.hero').style.display='none';
|
| 563 |
let dv=document.getElementById('detailView');
|
|
@@ -627,6 +639,7 @@ ${p.summary?`<div class="detail-summary">${p.summary}</div>`:''}
|
|
| 627 |
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
| 628 |
<button class="add-cart-btn" onclick="event.stopPropagation();addToCart(${idx})"><i class="fas fa-cart-plus"></i> Thêm vào giỏ</button>
|
| 629 |
<a href="${p.link}" target="_blank" class="detail-btn detail-btn-outline"><i class="fas fa-external-link-alt"></i> ${p.brand==='Eurogold'?'eurogold.vn':p.brand==='Grob'?'grob.vn':'malloca.com'}</a>
|
|
|
|
| 630 |
</div>
|
| 631 |
</div>
|
| 632 |
</div>
|
|
@@ -644,6 +657,8 @@ document.getElementById('detailView').classList.remove('show');
|
|
| 644 |
document.getElementById('detailView').innerHTML='';
|
| 645 |
document.getElementById('listView').style.display='';
|
| 646 |
document.querySelector('.hero').style.display='';
|
|
|
|
|
|
|
| 647 |
}
|
| 648 |
|
| 649 |
function goHome(){
|
|
@@ -927,6 +942,17 @@ function parseUrlParams(){
|
|
| 927 |
let hash=location.hash.replace(/^#/,'');
|
| 928 |
let params=new URLSearchParams(hash);
|
| 929 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 930 |
// Check for page navigation first
|
| 931 |
let pageParam=params.get('page');
|
| 932 |
if(pageParam&&pageParam!=='products'){
|
|
@@ -941,9 +967,9 @@ S.q=q;
|
|
| 941 |
S.pg=1;
|
| 942 |
}
|
| 943 |
let cat=params.get('cat');
|
| 944 |
-
if(cat){S.cat=cat;}
|
| 945 |
let brand=params.get('brand');
|
| 946 |
-
if(brand){S.brand=brand;}
|
| 947 |
let pgNum=params.get('pg');
|
| 948 |
if(pgNum){S.pg=parseInt(pgNum)||1;}
|
| 949 |
let sort=params.get('sort');
|
|
@@ -974,22 +1000,85 @@ location.hash='page='+page;
|
|
| 974 |
window.addEventListener('hashchange',()=>{
|
| 975 |
let hash=location.hash.replace(/^#/,'');
|
| 976 |
let params=new URLSearchParams(hash);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 977 |
let pageParam=params.get('page');
|
| 978 |
if(pageParam&&pageParam!=='products'){
|
| 979 |
showPage(pageParam);
|
| 980 |
return;
|
| 981 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 982 |
parseUrlParams();
|
| 983 |
render();
|
| 984 |
});
|
| 985 |
|
| 986 |
// Init on load
|
| 987 |
document.addEventListener('DOMContentLoaded',()=>{
|
| 988 |
-
parseUrlParams()
|
| 989 |
initCatalogue();
|
| 990 |
updateCartBadge();
|
| 991 |
});
|
| 992 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 993 |
</script>
|
| 994 |
</body>
|
| 995 |
</html>
|
|
|
|
| 313 |
.cart-drawer{width:100%;max-width:100%}
|
| 314 |
}
|
| 315 |
|
| 316 |
+
/* SHARE BUTTON */
|
| 317 |
+
.share-btn{position:fixed;bottom:24px;right:24px;z-index:90;display:flex;align-items:center;gap:8px;padding:12px 20px;background:var(--p);color:#fff;border:none;border-radius:14px;font-weight:700;font-size:.82rem;cursor:pointer;font-family:inherit;box-shadow:0 4px 16px rgba(0,63,98,.35);transition:var(--t)}
|
| 318 |
+
.share-btn:hover{background:var(--pl);transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,63,98,.4)}
|
| 319 |
+
.share-btn i{font-size:.9rem}
|
| 320 |
+
.share-toast{position:fixed;bottom:80px;right:24px;z-index:91;background:#28a745;color:#fff;padding:10px 18px;border-radius:10px;font-size:.82rem;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.2);animation:fadeUp .3s ease;pointer-events:none}
|
| 321 |
+
@media(max-width:768px){.share-btn{bottom:16px;right:16px;padding:10px 16px;font-size:.78rem}.share-toast{bottom:70px;right:16px}}
|
| 322 |
+
|
| 323 |
</style>
|
| 324 |
</head>
|
| 325 |
<body>
|
|
|
|
| 471 |
</div>
|
| 472 |
</div>
|
| 473 |
|
| 474 |
+
<!-- SHARE BUTTON -->
|
| 475 |
+
<button class="share-btn" onclick="copyShareLink()" title="Sao chép link chia sẻ trang hiện tại"><i class="fas fa-share-alt"></i> Chia sẻ trang này</button>
|
| 476 |
+
|
| 477 |
<footer class="footer"><div class="container">
|
| 478 |
<div class="footer-grid">
|
| 479 |
<div class="footer-brand"><div class="logo-name" style="color:#fff;font-size:1.1rem">MALL<span style="color:var(--a)">OCA</span> & EURO<span style="color:var(--a)">GOLD</span> & GR<span style="color:var(--a)">OB</span></div><p>1979 sản phẩm với dữ liệu chi tiết đầy đủ — hình ảnh, thông số kỹ thuật, mô tả, tính năng — tích hợp từ malloca.com, eurogold.vn & catalogue Grob.</p>
|
|
|
|
| 503 |
return nVN(parts.join(' '));
|
| 504 |
}
|
| 505 |
|
| 506 |
+
function init(){buildCats();buildPrices();buildBrands();render();parseUrlParams();render()}
|
| 507 |
|
| 508 |
function buildCats(){
|
| 509 |
let cats={};D.forEach(p=>{if(!cats[p.catSlug])cats[p.catSlug]={n:p.cat,i:p.catIcon,c:0};cats[p.catSlug].c++});
|
|
|
|
| 568 |
// ===== DETAIL VIEW =====
|
| 569 |
function showDetail(idx){
|
| 570 |
let p=D[idx];
|
| 571 |
+
// Update hash with product index for deep linking
|
| 572 |
+
location.hash='product='+idx;
|
| 573 |
document.getElementById('listView').style.display='none';
|
| 574 |
document.querySelector('.hero').style.display='none';
|
| 575 |
let dv=document.getElementById('detailView');
|
|
|
|
| 639 |
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
| 640 |
<button class="add-cart-btn" onclick="event.stopPropagation();addToCart(${idx})"><i class="fas fa-cart-plus"></i> Thêm vào giỏ</button>
|
| 641 |
<a href="${p.link}" target="_blank" class="detail-btn detail-btn-outline"><i class="fas fa-external-link-alt"></i> ${p.brand==='Eurogold'?'eurogold.vn':p.brand==='Grob'?'grob.vn':'malloca.com'}</a>
|
| 642 |
+
<button class="detail-btn detail-btn-outline" onclick="event.stopPropagation();copyShareLink()" title="Sao chép link sản phẩm"><i class="fas fa-share-alt"></i> Chia sẻ</button>
|
| 643 |
</div>
|
| 644 |
</div>
|
| 645 |
</div>
|
|
|
|
| 657 |
document.getElementById('detailView').innerHTML='';
|
| 658 |
document.getElementById('listView').style.display='';
|
| 659 |
document.querySelector('.hero').style.display='';
|
| 660 |
+
// Remove product hash, restore list hash
|
| 661 |
+
updateHash();
|
| 662 |
}
|
| 663 |
|
| 664 |
function goHome(){
|
|
|
|
| 942 |
let hash=location.hash.replace(/^#/,'');
|
| 943 |
let params=new URLSearchParams(hash);
|
| 944 |
|
| 945 |
+
// Check for product detail deep link
|
| 946 |
+
let productIdx=params.get('product');
|
| 947 |
+
if(productIdx!==null){
|
| 948 |
+
let idx=parseInt(productIdx);
|
| 949 |
+
if(!isNaN(idx)&&idx>=0&&idx<D.length){
|
| 950 |
+
showPage('products');
|
| 951 |
+
showDetail(idx);
|
| 952 |
+
return;
|
| 953 |
+
}
|
| 954 |
+
}
|
| 955 |
+
|
| 956 |
// Check for page navigation first
|
| 957 |
let pageParam=params.get('page');
|
| 958 |
if(pageParam&&pageParam!=='products'){
|
|
|
|
| 967 |
S.pg=1;
|
| 968 |
}
|
| 969 |
let cat=params.get('cat');
|
| 970 |
+
if(cat){S.cat=cat;updActive('.cat-item','data-s',cat);}
|
| 971 |
let brand=params.get('brand');
|
| 972 |
+
if(brand){S.brand=brand;document.querySelectorAll('[data-b]').forEach(e=>e.classList.toggle('active',e.getAttribute('data-b')===brand));}
|
| 973 |
let pgNum=params.get('pg');
|
| 974 |
if(pgNum){S.pg=parseInt(pgNum)||1;}
|
| 975 |
let sort=params.get('sort');
|
|
|
|
| 1000 |
window.addEventListener('hashchange',()=>{
|
| 1001 |
let hash=location.hash.replace(/^#/,'');
|
| 1002 |
let params=new URLSearchParams(hash);
|
| 1003 |
+
|
| 1004 |
+
// Handle product detail deep link
|
| 1005 |
+
let productIdx=params.get('product');
|
| 1006 |
+
if(productIdx!==null){
|
| 1007 |
+
let idx=parseInt(productIdx);
|
| 1008 |
+
if(!isNaN(idx)&&idx>=0&&idx<D.length){
|
| 1009 |
+
showPage('products');
|
| 1010 |
+
// Only re-render detail if not already showing this product
|
| 1011 |
+
let dv=document.getElementById('detailView');
|
| 1012 |
+
if(!dv.classList.contains('show')){
|
| 1013 |
+
showDetail(idx);
|
| 1014 |
+
}
|
| 1015 |
+
return;
|
| 1016 |
+
}
|
| 1017 |
+
}
|
| 1018 |
+
|
| 1019 |
let pageParam=params.get('page');
|
| 1020 |
if(pageParam&&pageParam!=='products'){
|
| 1021 |
showPage(pageParam);
|
| 1022 |
return;
|
| 1023 |
}
|
| 1024 |
+
// If hash is empty or products page, go back to list
|
| 1025 |
+
if(!hash||pageParam==='products'){
|
| 1026 |
+
goBackToList();
|
| 1027 |
+
showPage('products');
|
| 1028 |
+
}
|
| 1029 |
parseUrlParams();
|
| 1030 |
render();
|
| 1031 |
});
|
| 1032 |
|
| 1033 |
// Init on load
|
| 1034 |
document.addEventListener('DOMContentLoaded',()=>{
|
| 1035 |
+
// parseUrlParams is called from init() after data loads
|
| 1036 |
initCatalogue();
|
| 1037 |
updateCartBadge();
|
| 1038 |
});
|
| 1039 |
|
| 1040 |
+
// ===== SHARE LINK =====
|
| 1041 |
+
function getShareableUrl(){
|
| 1042 |
+
// Build the direct .hf.space URL with current hash — this URL works when shared
|
| 1043 |
+
let base=window.location.origin+window.location.pathname;
|
| 1044 |
+
let hash=window.location.hash;
|
| 1045 |
+
return base+(hash||'');
|
| 1046 |
+
}
|
| 1047 |
+
|
| 1048 |
+
function copyShareLink(){
|
| 1049 |
+
let url=getShareableUrl();
|
| 1050 |
+
if(navigator.clipboard&&navigator.clipboard.writeText){
|
| 1051 |
+
navigator.clipboard.writeText(url).then(()=>showShareToast('Đã sao chép link!')).catch(()=>fallbackCopy(url));
|
| 1052 |
+
}else{fallbackCopy(url)}
|
| 1053 |
+
}
|
| 1054 |
+
|
| 1055 |
+
function fallbackCopy(text){
|
| 1056 |
+
let ta=document.createElement('textarea');
|
| 1057 |
+
ta.value=text;ta.style.cssText='position:fixed;top:-9999px';
|
| 1058 |
+
document.body.appendChild(ta);ta.select();
|
| 1059 |
+
try{document.execCommand('copy');showShareToast('Đã sao chép link!')}catch(e){showShareToast('Không thể sao chép. URL: '+text)}
|
| 1060 |
+
document.body.removeChild(ta);
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
function showShareToast(msg){
|
| 1064 |
+
let old=document.querySelector('.share-toast');if(old)old.remove();
|
| 1065 |
+
let t=document.createElement('div');t.className='share-toast';
|
| 1066 |
+
t.innerHTML='<i class="fas fa-check-circle" style="margin-right:6px"></i>'+msg;
|
| 1067 |
+
document.body.appendChild(t);
|
| 1068 |
+
setTimeout(()=>{t.style.opacity='0';t.style.transition='opacity .3s';setTimeout(()=>t.remove(),300)},2500);
|
| 1069 |
+
}
|
| 1070 |
+
|
| 1071 |
+
// ===== SYNC HASH TO PARENT (forward-compatible) =====
|
| 1072 |
+
function syncHashToParent(){
|
| 1073 |
+
try{
|
| 1074 |
+
if(window.parent!==window){
|
| 1075 |
+
window.parent.postMessage({type:'hashchange',hash:location.hash,url:getShareableUrl()},'*');
|
| 1076 |
+
}
|
| 1077 |
+
}catch(e){}
|
| 1078 |
+
}
|
| 1079 |
+
// Sync on every hash change
|
| 1080 |
+
window.addEventListener('hashchange',syncHashToParent);
|
| 1081 |
+
|
| 1082 |
</script>
|
| 1083 |
</body>
|
| 1084 |
</html>
|