| <!DOCTYPE html> |
| <html lang="vi"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width,initial-scale=1"> |
| <title>V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi</title> |
| <meta name="description" content="V.AI STUDIO — 8000+ sản phẩm thiết bị nhà bếp & điện máy. Malloca, Eurogold, Grob, Canzy, Demax & Điện Máy Xanh chính hãng."> |
| <meta property="og:type" content="website"> |
| <meta property="og:site_name" content="V.AI STUDIO"> |
| <meta property="og:title" id="ogTitle" content="V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi"> |
| <meta property="og:description" id="ogDesc" content="8000+ sản phẩm thiết bị nhà bếp & điện máy chính hãng. Malloca, Eurogold, Grob, Canzy, Demax & Điện Máy Xanh."> |
| <meta property="og:image" id="ogImage" content="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_600.png"> |
| <meta property="og:url" id="ogUrl" content="https://bep40-v-aistudio.static.hf.space/"> |
| <meta name="twitter:card" content="summary_large_image"> |
| <meta name="twitter:title" id="twTitle" content="V.AI STUDIO"> |
| <meta name="twitter:description" id="twDesc" content="8000+ sản phẩm thiết bị nhà bếp & điện máy chính hãng"> |
| <meta name="twitter:image" id="twImage" content="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_600.png"> |
| <link rel="icon" href="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/favicon.png"> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> |
| <style> |
| *{margin:0;padding:0;box-sizing:border-box} |
| :root{--p:#003f62;--pl:#005a8c;--a:#db9815;--al:#f0b840;--d:#0f172a;--l:#f8fafc;--g:#64748b;--gl:#e2e8f0;--w:#fff;--r:14px;--t:all .25s ease} |
| body{font-family:'Inter',system-ui,sans-serif;color:var(--d);background:var(--l);overflow-x:hidden} |
| ::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--p);border-radius:3px} |
| img{max-width:100%;display:block}a{text-decoration:none;color:inherit} |
| .container{max-width:1360px;margin:0 auto;padding:0 20px} |
|
|
| /* TOPBAR */ |
| .topbar{background:var(--p);color:#fff;padding:6px 0;font-size:.72rem} |
| .topbar .container{display:flex;justify-content:center;gap:28px;flex-wrap:wrap} |
| .topbar-item{display:flex;align-items:center;gap:6px} |
| .topbar-item i{color:var(--a);font-size:.65rem} |
|
|
| /* HEADER */ |
| .header{background:#fff;position:sticky;top:0;z-index:100;box-shadow:0 2px 12px rgba(0,0,0,.06)} |
| .header .container{display:flex;align-items:center;justify-content:space-between;padding:10px 20px;gap:14px} |
| .logo{display:flex;align-items:center;gap:10px;flex-shrink:0;cursor:pointer} |
| .logo-box{width:40px;height:40px;background:var(--p);border-radius:10px;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:900;font-size:1rem} |
| .logo-name{font-size:1.3rem;font-weight:900;color:var(--p);letter-spacing:1.5px} |
| .logo-name span{color:var(--a)} |
| .logo-sub{font-size:.55rem;color:var(--g);letter-spacing:2px;text-transform:uppercase} |
|
|
| /* SEARCH */ |
| .search-box{flex:1;max-width:480px;position:relative} |
| .search-box input{width:100%;padding:9px 14px 9px 38px;border:2px solid var(--gl);border-radius:10px;font-size:.85rem;font-family:inherit;background:#fff;transition:var(--t);outline:none} |
| .search-box input:focus{border-color:var(--p);box-shadow:0 0 0 3px rgba(0,63,98,.08)} |
| .search-box>i{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--g);font-size:.85rem} |
| .search-count{position:absolute;right:10px;top:50%;transform:translateY(-50%);font-size:.68rem;color:var(--g);background:var(--l);padding:2px 7px;border-radius:6px} |
|
|
| .nav-icons{display:flex;gap:6px;flex-shrink:0} |
| .nav-icons a{width:36px;height:36px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--p);background:rgba(0,63,98,.05);transition:var(--t);font-size:.85rem} |
| .nav-icons a:hover{background:var(--p);color:#fff} |
| .hamburger{display:none;background:none;border:none;cursor:pointer;padding:6px} |
| .hamburger span{display:block;width:20px;height:2px;background:var(--p);margin:4px 0} |
|
|
| /* HERO */ |
| .hero{background:linear-gradient(135deg,var(--p),var(--pl),#0077b6);padding:40px 0;text-align:center;position:relative;overflow:hidden} |
| .hero::after{content:'';position:absolute;top:-40%;right:-15%;width:500px;height:500px;background:radial-gradient(circle,rgba(219,152,21,.1),transparent 70%);border-radius:50%} |
| .hero .container{position:relative;z-index:2} |
| .hero h1{font-size:2.2rem;color:#fff;font-weight:900;margin-bottom:10px}.hero h1 span{color:var(--al)} |
| .hero p{color:rgba(255,255,255,.75);font-size:.9rem;max-width:550px;margin:0 auto 20px;line-height:1.5} |
| .hero-stats{display:flex;justify-content:center;gap:36px;flex-wrap:wrap} |
| .hero-stat{color:#fff;text-align:center}.hero-stat strong{display:block;font-size:1.6rem;font-weight:900}.hero-stat span{font-size:.7rem;opacity:.7} |
|
|
| /* LAYOUT */ |
| .main{display:flex;gap:22px;padding:28px 0} |
| .sidebar{width:260px;flex-shrink:0;position:sticky;top:76px;height:fit-content} |
| .content{flex:1;min-width:0} |
|
|
| /* SIDEBAR */ |
| .sb-card{background:#fff;border-radius:var(--r);padding:18px;margin-bottom:14px;box-shadow:0 1px 4px rgba(0,0,0,.06)} |
| .sb-card h3{font-size:.8rem;font-weight:700;color:var(--d);margin-bottom:12px;display:flex;align-items:center;gap:7px} |
| .sb-card h3 i{color:var(--a);font-size:.75rem} |
| .cat-list{list-style:none} |
| .cat-item{padding:8px 10px;border-radius:9px;cursor:pointer;transition:var(--t);font-size:.78rem;font-weight:500;display:flex;align-items:center;gap:9px;color:var(--d);margin-bottom:1px} |
| .cat-item:hover{background:rgba(0,63,98,.04);color:var(--p)} |
| .cat-item.active{background:var(--p);color:#fff} |
| .cat-count{margin-left:auto;font-size:.65rem;opacity:.6;background:rgba(0,0,0,.05);padding:2px 7px;border-radius:20px} |
| .cat-item.active .cat-count{background:rgba(255,255,255,.2)} |
| .cat-item i{width:18px;text-align:center;font-size:.8rem} |
| .cat-sub{font-size:.72rem;padding:5px 10px 5px 18px;opacity:.85} |
| .cat-sub:hover{opacity:1} |
| .pr{display:flex;flex-direction:column;gap:3px} |
| .pr-item{padding:7px 10px;border-radius:8px;cursor:pointer;font-size:.76rem;transition:var(--t);display:flex;align-items:center;gap:7px} |
| .pr-item:hover{background:rgba(0,63,98,.04)} |
| .pr-item.active{background:var(--p);color:#fff} |
| .pr-dot{width:7px;height:7px;border-radius:50%;border:2px solid var(--gl)} |
| .pr-item.active .pr-dot{border-color:#fff;background:#fff} |
|
|
| /* TOOLBAR */ |
| .toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;flex-wrap:wrap;gap:10px} |
| .result-count{font-size:.82rem;color:var(--g)}.result-count strong{color:var(--d)} |
| .sort-select{padding:7px 12px;border:2px solid var(--gl);border-radius:9px;font-size:.78rem;font-family:inherit;background:#fff;cursor:pointer;outline:none;font-weight:500} |
| .view-btns{display:flex;gap:3px} |
| .view-btn{width:32px;height:32px;border:2px solid var(--gl);border-radius:7px;display:flex;align-items:center;justify-content:center;cursor:pointer;background:#fff;color:var(--g);transition:var(--t);font-size:.75rem} |
| .view-btn.active{border-color:var(--p);color:var(--p);background:rgba(0,63,98,.04)} |
|
|
| /* GRID */ |
| .pg{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:16px} |
| .pg.lv{grid-template-columns:1fr} |
| .pg.lv .pc{display:flex}.pg.lv .pc .pi{width:180px;height:140px;flex-shrink:0}.pg.lv .pc .pb{flex:1} |
|
|
| /* CARD */ |
| .pc{background:#fff;border-radius:var(--r);overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06);transition:var(--t);cursor:pointer;border:2px solid transparent} |
| .pc:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,.1);border-color:var(--p)} |
| .pi{height:200px;overflow:hidden;position:relative;background:#f1f5f9;display:flex;align-items:center;justify-content:center} |
| .pi img{width:100%;height:100%;object-fit:contain;transition:transform .35s ease;padding:6px} |
| .pc:hover .pi img{transform:scale(1.05)} |
| .pi-badge{position:absolute;top:8px;left:8px;background:var(--p);color:#fff;padding:3px 8px;border-radius:5px;font-size:.6rem;font-weight:700} |
| .pb{padding:14px} |
| .pn{font-size:.82rem;font-weight:600;color:var(--d);line-height:1.35;margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2.2em} |
| .pp{font-size:1rem;font-weight:800;color:var(--p)} |
| .pf{display:flex;align-items:center;justify-content:space-between;margin-top:8px} |
| .pf i{color:var(--a);font-size:.8rem} |
|
|
| /* PAGINATION */ |
| .pagination{display:flex;justify-content:center;gap:5px;margin-top:26px;flex-wrap:wrap} |
| .pgb{width:36px;height:36px;border:2px solid var(--gl);border-radius:9px;display:flex;align-items:center;justify-content:center;cursor:pointer;background:#fff;font-size:.82rem;font-weight:600;transition:var(--t)} |
| .pgb:hover{border-color:var(--p);color:var(--p)} |
| .pgb.active{background:var(--p);color:#fff;border-color:var(--p)} |
| .pgb:disabled{opacity:.3;cursor:default} |
| #catTabs{display:flex;gap:6px;padding:4px 0 12px;margin-bottom:16px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none} |
| #catTabs::-webkit-scrollbar{display:none} |
| #catTabs .ctab{display:inline-flex;align-items:center;gap:6px;padding:9px 16px;border:2px solid var(--gl);border-radius:50px;cursor:pointer;background:#fff;font-size:.78rem;font-weight:600;transition:var(--t);white-space:nowrap;flex-shrink:0;color:var(--d);line-height:1} |
| #catTabs .ctab:hover{border-color:var(--p);color:var(--p);background:rgba(0,63,98,.03)} |
| #catTabs .ctab.active{background:var(--p);color:#fff;border-color:var(--p)} |
| #catTabs .ctab i{font-size:.7rem} |
| #catTabs .ctab .cnt{font-size:.6rem;opacity:.65;margin-left:2px} |
| #catTabs .ctab.active .cnt{opacity:.8} |
| @media(max-width:768px){#catTabs .ctab{padding:7px 12px;font-size:.7rem}} |
| .pg-dots{display:flex;align-items:center;font-size:.8rem;color:var(--g);padding:0 4px} |
|
|
| /* ========== DETAIL VIEW ========== */ |
| #detailView{display:none;min-height:80vh} |
| #detailView.show{display:block} |
| #listView.hide{display:none} |
|
|
| .detail-back{display:inline-flex;align-items:center;gap:8px;color:var(--p);font-weight:600;font-size:.85rem;cursor:pointer;padding:8px 0;margin-bottom:20px;transition:var(--t)} |
| .detail-back:hover{color:var(--a)} |
|
|
| .detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:36px;background:#fff;border-radius:20px;padding:32px;box-shadow:0 2px 16px rgba(0,0,0,.06)} |
|
|
| /* Gallery — Carousel */ |
| .gallery{position:relative;width:100%} |
| .gallery-carousel{position:relative;width:100%;overflow:hidden;border-radius:16px;background:#f8fafc;margin-bottom:10px} |
| .gallery-track{display:flex;transition:transform .3s ease;will-change:transform} |
| .gallery-slide{min-width:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;max-height:75vh} |
| .gallery-slide img{display:block;width:100%;height:auto;max-height:75vh;object-fit:contain} |
| .gallery-nav{position:absolute;top:50%;transform:translateY(-50%);width:36px;height:36px;border-radius:50%;background:rgba(0,0,0,.45);color:#fff;border:none;cursor:pointer;font-size:1rem;display:flex;align-items:center;justify-content:center;z-index:3;opacity:0;transition:opacity .2s} |
| .gallery-carousel:hover .gallery-nav{opacity:1} |
| .gallery-nav.gn-prev{left:8px} |
| .gallery-nav.gn-next{right:8px} |
| .gallery-dots{display:flex;justify-content:center;gap:6px;padding:6px 0} |
| .gallery-dot{width:8px;height:8px;border-radius:50%;background:var(--gl);cursor:pointer;transition:var(--t)} |
| .gallery-dot.active{background:var(--p);width:20px;border-radius:4px} |
| .gallery-thumbs{display:flex;flex-wrap:wrap;gap:6px;padding-bottom:6px} |
| .gallery-thumbs::-webkit-scrollbar{display:none} |
| .gallery-thumb{width:56px;height:56px;min-width:56px;border-radius:8px;overflow:hidden;border:2px solid var(--gl);cursor:pointer;flex-shrink:0;transition:var(--t);background:#f8fafc} |
| .gallery-thumb.active{border-color:var(--p)} |
| .gallery-thumb img{width:100%;height:100%;object-fit:contain;padding:2px} |
| @media(max-width:768px){ |
| .gallery-nav{opacity:.7;width:32px;height:32px;font-size:.85rem} |
| .gallery-slide{max-height:60vh} |
| .gallery-slide img{max-height:60vh} |
| .gallery-thumbs{gap:5px} |
| .gallery-thumb{width:48px;height:48px;min-width:48px} |
| } |
|
|
| /* Info */ |
| .detail-info{} |
| .detail-sku{font-size:.72rem;color:var(--g);margin-bottom:6px;font-weight:500} |
| .detail-cat{display:inline-flex;align-items:center;gap:6px;background:rgba(0,63,98,.06);color:var(--p);padding:5px 14px;border-radius:50px;font-size:.72rem;font-weight:700;margin-bottom:14px} |
| .detail-name{font-size:1.6rem;font-weight:800;color:var(--d);line-height:1.3;margin-bottom:16px} |
| .detail-price{font-size:1.8rem;font-weight:900;color:var(--p);margin-bottom:20px} |
| .detail-summary{font-size:.88rem;color:var(--g);line-height:1.7;margin-bottom:24px;white-space:pre-line} |
| .detail-badges{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px} |
| .detail-badge{display:flex;align-items:center;gap:6px;background:rgba(0,63,98,.04);padding:8px 14px;border-radius:10px;font-size:.78rem;font-weight:500;color:var(--d)} |
| .detail-badge i{color:var(--a);font-size:.75rem} |
| .detail-btn{display:inline-flex;align-items:center;gap:8px;padding:14px 28px;background:var(--p);color:#fff;border-radius:12px;font-weight:700;font-size:.88rem;transition:var(--t);border:none;cursor:pointer;font-family:inherit} |
| .detail-btn:hover{background:var(--pl);transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,63,98,.25)} |
| .detail-btn-outline{background:transparent;border:2px solid var(--p);color:var(--p)} |
| .detail-btn-outline:hover{background:var(--p);color:#fff} |
|
|
| /* TABS */ |
| .dtabs{margin-top:30px;background:#fff;border-radius:20px;padding:0;box-shadow:0 2px 16px rgba(0,0,0,.06);overflow:hidden} |
| .dtab-headers{display:flex;border-bottom:2px solid var(--gl);overflow-x:auto} |
| .dtab-h{padding:16px 28px;font-size:.85rem;font-weight:600;color:var(--g);cursor:pointer;transition:var(--t);border-bottom:3px solid transparent;white-space:nowrap;flex-shrink:0} |
| .dtab-h:hover{color:var(--p)} |
| .dtab-h.active{color:var(--p);border-color:var(--a);background:rgba(0,63,98,.02)} |
| .dtab-body{padding:28px} |
| .dtab-content{display:none} |
| .dtab-content.active{display:block} |
|
|
| /* Specs table */ |
| .specs-table{width:100%;border-collapse:collapse} |
| .specs-table tr:nth-child(even){background:#f8fafc} |
| .specs-table td{padding:11px 16px;font-size:.84rem;border-bottom:1px solid var(--gl)} |
| .specs-table td:first-child{font-weight:600;color:var(--d);width:40%;background:rgba(0,63,98,.02)} |
| .specs-table td:last-child{color:var(--g)} |
|
|
| /* Features list */ |
| .feat-list{list-style:none;columns:2;column-gap:24px} |
| .feat-item{padding:8px 0;font-size:.84rem;color:var(--d);display:flex;align-items:flex-start;gap:8px;break-inside:avoid} |
| .feat-item i{color:var(--a);margin-top:3px;font-size:.7rem;flex-shrink:0} |
|
|
| /* Description */ |
| .desc-text{font-size:.88rem;color:var(--d);line-height:1.8;white-space:pre-line;overflow:hidden;word-break:break-word} |
| .desc-text img,.dtab-body img,.specs-table img{max-width:100%;height:auto} |
|
|
| /* Video */ |
| .video-wrap{position:relative;padding-bottom:56.25%;height:0;border-radius:16px;overflow:hidden} |
| .video-wrap iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none} |
|
|
| /* Empty */ |
| .empty{text-align:center;padding:50px 20px;color:var(--g)} |
| .empty i{font-size:2.5rem;opacity:.3;margin-bottom:14px;display:block} |
| .empty h3{font-size:1rem;font-weight:700;color:var(--d);margin-bottom:6px} |
| .reset-btn{margin-top:14px;padding:9px 22px;background:var(--p);color:#fff;border:none;border-radius:9px;cursor:pointer;font-weight:600;font-size:.82rem;font-family:inherit} |
|
|
| /* MOBILE SIDEBAR */ |
| .msb{display:none;position:fixed;inset:0;background:#fff;z-index:150;overflow-y:auto;padding:18px;flex-direction:column} |
| .msb.open{display:flex} |
| .msb-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px} |
| .msb-close{width:34px;height:34px;border:none;background:var(--gl);border-radius:9px;cursor:pointer;font-size:.95rem} |
|
|
| /* FOOTER */ |
| .footer{background:var(--d);color:rgba(255,255,255,.65);padding:44px 0 20px;margin-top:36px} |
| .footer-grid{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:30px;margin-bottom:30px} |
| .footer h4{color:#fff;font-size:.78rem;font-weight:700;margin-bottom:14px;text-transform:uppercase;letter-spacing:1px} |
| .footer-links{list-style:none}.footer-links li{margin-bottom:7px} |
| .footer-links a{color:rgba(255,255,255,.5);font-size:.78rem;transition:var(--t)}.footer-links a:hover{color:var(--a)} |
| .footer-brand p{font-size:.78rem;line-height:1.6;margin:8px 0 14px} |
| .social-links{display:flex;gap:7px} |
| .social-links a{width:34px;height:34px;border-radius:8px;background:rgba(255,255,255,.07);display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,.55);transition:var(--t);font-size:.8rem} |
| .social-links a:hover{background:var(--a);color:#fff} |
| .footer-bottom{border-top:1px solid rgba(255,255,255,.07);padding-top:16px;text-align:center;font-size:.7rem} |
|
|
| /* RESPONSIVE */ |
| @media(max-width:1024px){.sidebar{width:230px}.pg{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}.detail-grid{grid-template-columns:1fr;gap:24px}.feat-list{columns:1}} |
| @media(max-width:768px){ |
| .sidebar{display:none}.hamburger{display:block} |
| .hero h1{font-size:1.6rem}.hero-stats{gap:20px}.hero-stat strong{font-size:1.2rem} |
| .search-box{max-width:none;order:3;flex-basis:100%}.header .container{flex-wrap:wrap} |
| .pg{grid-template-columns:repeat(2,1fr);gap:10px}.pi{height:150px}.pb{padding:10px}.pn{font-size:.75rem}.pp{font-size:.88rem} |
| .detail-grid{grid-template-columns:1fr;padding:16px}.detail-name{font-size:1.2rem}.detail-price{font-size:1.4rem} |
| .detail-info{word-break:break-word} |
| .dtab-h{padding:12px 18px;font-size:.8rem} |
| .footer-grid{grid-template-columns:1fr 1fr} |
| } |
|
|
| .fade{opacity:0;transform:translateY(14px);transition:opacity .35s,transform .35s}.fade.vis{opacity:1;transform:none} |
| @keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}} |
|
|
| /* ===== NAV TABS ===== */ |
| .nav-tabs{display:flex;gap:2px;margin-left:16px} |
| .nav-tab{padding:8px 14px;border-radius:9px;font-size:.78rem;font-weight:600;cursor:pointer;transition:var(--t);color:var(--g);position:relative} |
| .nav-tab:hover{color:var(--p);background:rgba(0,63,98,.04)} |
| .nav-tab.active{color:var(--p);background:rgba(0,63,98,.08)} |
|
|
| /* ===== CART BADGE ===== */ |
| .cart-badge{position:relative} |
| .cart-num{position:absolute;top:-4px;right:-4px;background:var(--a);color:#fff;font-size:.55rem;font-weight:800;width:16px;height:16px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:2px solid #fff} |
|
|
| /* ===== PAGE SECTIONS ===== */ |
| .page-section{display:none;padding:28px 0;min-height:60vh} |
| .page-section.active{display:block} |
|
|
| /* ===== CATALOGUE ===== */ |
| .cat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:20px;margin-top:24px} |
| .cat-card{background:#fff;border-radius:var(--r);overflow:hidden;box-shadow:0 2px 10px rgba(0,0,0,.06);transition:var(--t);cursor:pointer;border:2px solid transparent} |
| .cat-card:hover{border-color:var(--p);transform:translateY(-4px);box-shadow:0 8px 24px rgba(0,0,0,.1)} |
| .cat-card img{width:100%;height:200px;object-fit:cover} |
| .cat-card-body{padding:16px;text-align:center} |
| .cat-card-body h3{font-size:.95rem;font-weight:700;color:var(--d);margin-bottom:6px} |
| .cat-card-body p{font-size:.78rem;color:var(--g)} |
| .cat-iframe-wrap{margin-top:20px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 2px 16px rgba(0,0,0,.06)} |
| .cat-iframe-wrap iframe{width:100%;height:80vh;border:none} |
| .cat-back{display:inline-flex;align-items:center;gap:8px;color:var(--p);font-weight:600;font-size:.85rem;cursor:pointer;padding:8px 0;margin-bottom:16px} |
|
|
| /* ===== CONTACT ===== */ |
| .contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:30px;margin-top:24px} |
| .contact-card{background:#fff;border-radius:var(--r);padding:28px;box-shadow:0 2px 10px rgba(0,0,0,.06)} |
| .contact-card h3{font-size:1.1rem;font-weight:700;color:var(--d);margin-bottom:20px;display:flex;align-items:center;gap:10px} |
| .contact-card h3 i{color:var(--a)} |
| .contact-info{list-style:none} |
| .contact-info li{display:flex;align-items:flex-start;gap:12px;padding:12px 0;border-bottom:1px solid var(--gl);font-size:.88rem;color:var(--d)} |
| .contact-info li:last-child{border:none} |
| .contact-info li i{color:var(--p);width:20px;text-align:center;margin-top:3px} |
| .form-group{margin-bottom:16px} |
| .form-group label{display:block;font-size:.82rem;font-weight:600;color:var(--d);margin-bottom:6px} |
| .form-input{width:100%;padding:10px 14px;border:2px solid var(--gl);border-radius:10px;font-size:.85rem;font-family:inherit;outline:none;transition:var(--t)} |
| .form-input:focus{border-color:var(--p);box-shadow:0 0 0 3px rgba(0,63,98,.08)} |
| textarea.form-input{height:120px;resize:vertical} |
| .form-btn{padding:12px 28px;background:var(--p);color:#fff;border:none;border-radius:10px;font-weight:700;font-size:.88rem;cursor:pointer;font-family:inherit;transition:var(--t);width:100%} |
| .form-btn:hover{background:var(--pl)} |
| .map-wrap{margin-top:20px;border-radius:12px;overflow:hidden;height:280px} |
| .map-wrap iframe{width:100%;height:100%;border:none} |
|
|
| /* ===== CART DRAWER ===== */ |
| .cart-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:200;backdrop-filter:blur(3px)} |
| .cart-overlay.open{display:block} |
| .cart-drawer{position:fixed;top:0;right:0;width:400px;max-width:90vw;height:100vh;height:100dvh;background:#fff;z-index:201;display:flex;flex-direction:column;box-shadow:-4px 0 30px rgba(0,0,0,.15);transform:translateX(100%);transition:transform .35s ease;visibility:hidden} |
| .cart-drawer.open{transform:translateX(0);visibility:visible} |
| .cart-header{padding:20px;border-bottom:2px solid var(--gl);display:flex;align-items:center;justify-content:space-between} |
| .cart-header h3{font-size:1rem;font-weight:700;display:flex;align-items:center;gap:8px} |
| .cart-header h3 i{color:var(--a)} |
| .cart-close{width:34px;height:34px;border:none;background:var(--gl);border-radius:9px;cursor:pointer;font-size:.9rem} |
| .cart-items{flex:1;overflow-y:auto;padding:16px;min-height:0} |
| .cart-item{display:flex;gap:12px;padding:12px;background:var(--l);border-radius:12px;margin-bottom:10px} |
| .cart-item img{width:70px;height:70px;object-fit:contain;border-radius:8px;background:#fff;flex-shrink:0} |
| .cart-item-info{flex:1;min-width:0} |
| .cart-item-name{font-size:.8rem;font-weight:600;color:var(--d);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} |
| .cart-item-price{font-size:.88rem;font-weight:800;color:var(--p);margin-top:4px} |
| .cart-item-qty{display:flex;align-items:center;gap:8px;margin-top:6px} |
| .qty-btn{width:26px;height:26px;border:1px solid var(--gl);border-radius:6px;background:#fff;cursor:pointer;font-size:.8rem;display:flex;align-items:center;justify-content:center} |
| .qty-val{font-size:.82rem;font-weight:700;min-width:20px;text-align:center} |
| .cart-item-del{background:none;border:none;color:#dc3545;cursor:pointer;font-size:.85rem;margin-left:auto;padding:4px} |
| .cart-empty{text-align:center;padding:40px;color:var(--g)} |
| .cart-empty i{font-size:2.5rem;opacity:.3;display:block;margin-bottom:12px} |
| .cart-footer{padding:10px 14px;border-top:2px solid var(--gl);background:#fff;flex-shrink:0;max-height:none} |
| .cart-footer-btns{display:flex;gap:6px;margin-top:6px} |
| .cart-footer-btns .cart-checkout-btn{flex:1;padding:10px 6px;font-size:.78rem;border-radius:10px} |
| .cart-total{display:flex;justify-content:space-between;font-size:.92rem;font-weight:800;color:var(--d);margin-bottom:0} |
| .cart-checkout-btn{width:100%;padding:14px;background:var(--a);color:#fff;border:none;border-radius:12px;font-weight:700;font-size:.92rem;cursor:pointer;font-family:inherit;transition:var(--t);display:flex;align-items:center;justify-content:center;gap:8px} |
| .cart-checkout-btn:hover{background:var(--al);transform:translateY(-1px)} |
|
|
| /* ===== CHECKOUT MODAL ===== */ |
| .checkout-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:300;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(4px)} |
| .checkout-overlay.open{display:flex} |
| .checkout-modal{background:#fff;border-radius:20px;max-width:550px;width:100%;max-height:92vh;overflow-y:auto;padding:32px;position:relative} |
| .checkout-close{position:absolute;top:16px;right:16px;width:34px;height:34px;border:none;background:var(--gl);border-radius:9px;cursor:pointer;font-size:.9rem} |
| .checkout-title{font-size:1.3rem;font-weight:800;color:var(--d);margin-bottom:4px} |
| .checkout-sub{font-size:.82rem;color:var(--g);margin-bottom:24px} |
| .checkout-summary{background:var(--l);border-radius:12px;padding:16px;margin-bottom:20px} |
| .checkout-summary-item{display:flex;justify-content:space-between;font-size:.82rem;padding:4px 0;color:var(--d)} |
| .checkout-summary-item.total{font-weight:800;font-size:1rem;color:var(--p);border-top:2px solid var(--gl);padding-top:10px;margin-top:8px} |
| .qr-section{text-align:center;background:var(--l);border-radius:16px;padding:24px;margin:20px 0} |
| .qr-section img{max-width:260px;margin:0 auto 16px;border-radius:12px;border:3px solid var(--gl)} |
| .qr-bank-info{font-size:.82rem;color:var(--d);line-height:1.8} |
| .qr-bank-info strong{color:var(--p)} |
| .copy-btn{display:inline-flex;align-items:center;gap:4px;background:var(--p);color:#fff;border:none;padding:4px 10px;border-radius:6px;font-size:.7rem;cursor:pointer;font-family:inherit;margin-left:6px} |
| .checkout-note{font-size:.78rem;color:var(--g);text-align:center;margin-top:16px;line-height:1.6} |
| .checkout-done-btn{width:100%;padding:14px;background:var(--p);color:#fff;border:none;border-radius:12px;font-weight:700;font-size:.92rem;cursor:pointer;font-family:inherit;margin-top:16px} |
|
|
| /* ADD TO CART BTN (in detail) */ |
| .add-cart-btn{display:inline-flex;align-items:center;gap:8px;padding:14px 28px;background:var(--a);color:#fff;border-radius:12px;font-weight:700;font-size:.88rem;transition:var(--t);border:none;cursor:pointer;font-family:inherit} |
| .add-cart-btn:hover{background:var(--al);transform:translateY(-2px);box-shadow:0 8px 20px rgba(219,152,21,.3)} |
|
|
| @media(max-width:768px){ |
| .nav-tabs{display:none} |
| .contact-grid{grid-template-columns:1fr} |
| .cart-drawer{width:100%;max-width:100%;height:100vh;height:100dvh} |
| .cart-footer{padding:8px 12px} |
| .cart-footer-btns .cart-checkout-btn{padding:10px 4px;font-size:.76rem;gap:4px} |
| .cart-total{font-size:.88rem} |
| } |
|
|
| /* ===== AI CHATBOX ===== */ |
| .chat-fab{position:fixed;bottom:24px;right:24px;z-index:180;width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,var(--p),#0077b6);color:#fff;border:none;cursor:pointer;box-shadow:0 4px 20px rgba(0,63,98,.4);display:flex;align-items:center;justify-content:center;font-size:1.4rem;transition:var(--t)} |
| .chat-fab:hover{transform:scale(1.1);box-shadow:0 6px 28px rgba(0,63,98,.5)} |
| .chat-fab-label{position:fixed;bottom:32px;right:90px;z-index:180;background:var(--p);color:#fff;padding:6px 14px;border-radius:8px;font-size:.75rem;font-weight:600;white-space:nowrap;box-shadow:0 2px 8px rgba(0,0,0,.15);pointer-events:none;opacity:0;transition:opacity .3s} |
| .chat-fab:hover+.chat-fab-label{opacity:1} |
| .chat-overlay2{display:none;position:fixed;inset:0;background:rgba(0,0,0,.3);z-index:190;backdrop-filter:blur(2px)} |
| .chat-overlay2.open{display:block} |
| .chatbox{position:fixed;bottom:0;right:0;width:400px;max-width:100vw;height:560px;max-height:calc(100vh - 48px);background:#fff;border-radius:20px;box-shadow:0 8px 40px rgba(0,0,0,.18);z-index:200;display:none;flex-direction:column;overflow:hidden} |
| .chatbox.open{display:flex} |
| .chat-head{background:linear-gradient(135deg,var(--p),#0077b6);color:#fff;padding:14px 16px;padding-top:max(14px, env(safe-area-inset-top, 14px));display:flex;align-items:center;gap:12px;flex-shrink:0} |
| .chat-head-avatar{width:38px;height:38px;border-radius:12px;background:rgba(255,255,255,.15);display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0} |
| .chat-head-info h4{font-size:.9rem;font-weight:700}.chat-head-info span{font-size:.68rem;opacity:.75} |
| .chat-close{margin-left:auto;width:40px;height:40px;min-width:40px;min-height:40px;border-radius:50%;background:rgba(255,255,255,.25);border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:1.1rem;transition:var(--t)} |
| .chat-close:hover{background:rgba(255,255,255,.4)} |
| .chat-body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:#f8fafc} |
| .chat-msg{max-width:88%;padding:11px 15px;border-radius:16px;font-size:.84rem;line-height:1.55;word-wrap:break-word} |
| .chat-msg.bot{background:#fff;color:var(--d);align-self:flex-start;border:1px solid var(--gl);border-bottom-left-radius:4px} |
| .chat-msg.user{background:var(--p);color:#fff;align-self:flex-end;border-bottom-right-radius:4px} |
| .chat-msg a{color:var(--a);font-weight:600;text-decoration:underline} |
| .chat-msg.bot a{color:var(--p)} |
| .chat-typing{display:flex;gap:4px;padding:11px 15px;align-self:flex-start;background:#fff;border-radius:16px;border:1px solid var(--gl);border-bottom-left-radius:4px} |
| .chat-typing span{width:7px;height:7px;background:var(--g);border-radius:50%;animation:blink 1.4s infinite} |
| .chat-typing span:nth-child(2){animation-delay:.2s} |
| .chat-typing span:nth-child(3){animation-delay:.4s} |
| @keyframes blink{0%,80%,100%{opacity:.3}40%{opacity:1}} |
| .chat-suggestions{display:flex;flex-wrap:wrap;gap:6px;padding:0 16px 10px} |
| .chat-sug{padding:6px 12px;background:#fff;border:1.5px solid var(--gl);border-radius:20px;font-size:.75rem;font-weight:500;color:var(--p);cursor:pointer;transition:var(--t);white-space:nowrap} |
| .chat-sug:hover{border-color:var(--p);background:rgba(0,63,98,.04)} |
| .chat-foot{padding:10px 14px;padding-bottom:max(10px, env(safe-area-inset-bottom, 10px));border-top:1.5px solid var(--gl);display:flex;gap:8px;background:#fff;flex-shrink:0} |
| .chat-input{flex:1;padding:10px 14px;border:1.5px solid var(--gl);border-radius:12px;font-size:.85rem;font-family:inherit;outline:none;resize:none;max-height:80px;transition:var(--t)} |
| .chat-input:focus{border-color:var(--p)} |
| .chat-send{width:40px;height:40px;border-radius:10px;background:var(--p);color:#fff;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:.9rem;transition:var(--t);flex-shrink:0} |
| .chat-send:hover{background:var(--pl)} |
| .chat-send:disabled{opacity:.4;cursor:default} |
|
|
| /* ===== SHARE POPOVER ===== */ |
| .share-popover-overlay{display:none;position:fixed;inset:0;z-index:240;background:rgba(0,0,0,.35);backdrop-filter:blur(2px)} |
| .share-popover-overlay.open{display:flex;align-items:center;justify-content:center} |
| .share-popover{background:#fff;border-radius:18px;padding:24px;box-shadow:0 8px 40px rgba(0,0,0,.18);max-width:340px;width:calc(100vw - 40px)} |
| .share-popover h4{font-size:.92rem;font-weight:700;color:var(--d);margin-bottom:16px;display:flex;align-items:center;gap:8px} |
| .share-popover h4 i{color:var(--a)} |
| .share-close-btn{position:absolute;top:14px;right:14px;width:30px;height:30px;border:none;background:var(--gl);border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:.8rem} |
| .share-apps{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px} |
| .share-app{display:flex;flex-direction:column;align-items:center;gap:6px;cursor:pointer;transition:var(--t);padding:8px;border-radius:12px} |
| .share-app:hover{background:var(--l)} |
| .share-app-icon{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:1.15rem;color:#fff} |
| .share-app span{font-size:.65rem;font-weight:600;color:var(--g)} |
| .share-link-box{display:flex;gap:6px} |
| .share-link-input{flex:1;padding:9px 10px;border:1.5px solid var(--gl);border-radius:8px;font-size:.76rem;font-family:inherit;outline:none;background:var(--l);color:var(--d)} |
| .share-link-copy{padding:9px 14px;background:var(--p);color:#fff;border:none;border-radius:8px;font-size:.76rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;transition:var(--t)} |
| .share-link-copy:hover{background:var(--pl)} |
|
|
| @media(max-width:768px){ |
| .chatbox{bottom:0;right:0;width:100%;max-width:100%;height:100%;max-height:100vh;max-height:100dvh;border-radius:0;padding-top:0} |
| .chatbox .chat-head{padding-top:max(14px, env(safe-area-inset-top, 14px))} |
| .chat-fab{bottom:16px;right:16px;width:50px;height:50px;font-size:1.2rem} |
| .chat-fab-label{display:none} |
| } |
|
|
| /* ===== QUOTATION MODAL ===== */ |
| .quote-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:300;align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(4px)} |
| .quote-overlay.open{display:flex} |
| .quote-modal{background:#fff;border-radius:20px;max-width:960px;width:100%;max-height:92vh;overflow-y:auto;padding:0;position:relative} |
| .quote-head{background:linear-gradient(135deg,var(--p),#0077b6);color:#fff;padding:24px 28px;border-radius:20px 20px 0 0} |
| .quote-head h2{font-size:1.2rem;font-weight:800;margin-bottom:4px} |
| .quote-head p{font-size:.8rem;opacity:.75} |
| .quote-close{position:absolute;top:16px;right:16px;width:34px;height:34px;border:none;background:rgba(255,255,255,.15);border-radius:9px;cursor:pointer;color:#fff;font-size:.9rem;display:flex;align-items:center;justify-content:center} |
| .quote-body{padding:24px 28px} |
| .quote-section{margin-bottom:20px} |
| .quote-section h3{font-size:.88rem;font-weight:700;color:var(--d);margin-bottom:12px;display:flex;align-items:center;gap:8px} |
| .quote-section h3 i{color:var(--a);font-size:.8rem} |
| .quote-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px} |
| .quote-field label{display:block;font-size:.76rem;font-weight:600;color:var(--g);margin-bottom:4px} |
| .quote-field input{width:100%;padding:9px 12px;border:1.5px solid var(--gl);border-radius:8px;font-size:.84rem;font-family:inherit;outline:none} |
| .quote-field input:focus{border-color:var(--p)} |
| .quote-table{width:100%;border-collapse:collapse;font-size:.78rem;margin-top:8px} |
| .quote-table th{background:var(--p);color:#fff;padding:8px 6px;font-weight:600;text-align:center;white-space:nowrap} |
| .quote-table td{padding:8px 6px;border-bottom:1px solid var(--gl);vertical-align:middle} |
| .quote-table .qt-img{width:72px;height:72px;object-fit:contain;border-radius:6px;background:var(--l);border:1px solid var(--gl)} |
| .quote-table .qt-name{font-weight:600;color:var(--d)} |
| .quote-table .qt-price{text-align:right;white-space:nowrap} |
| .quote-table .qt-disc{width:80px;padding:5px 6px;border:1.5px solid var(--gl);border-radius:6px;font-size:.78rem;text-align:right;font-family:inherit;outline:none} |
| .quote-table .qt-disc:focus{border-color:var(--p)} |
| .quote-table .qt-note{width:100%;padding:5px 6px;border:1.5px solid var(--gl);border-radius:6px;font-size:.78rem;font-family:inherit;outline:none} |
| .quote-total-row td{font-weight:800;font-size:.88rem;color:var(--p);border-top:2px solid var(--p)} |
| .quote-actions{display:flex;gap:10px;margin-top:20px;flex-wrap:wrap;justify-content:center} |
| .quote-btn{padding:12px 24px;border:none;border-radius:10px;font-weight:700;font-size:.85rem;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:8px;transition:var(--t)} |
| .quote-btn-excel{background:#217346;color:#fff}.quote-btn-excel:hover{background:#1a5c38} |
| .quote-btn-pdf{background:#dc3545;color:#fff}.quote-btn-pdf:hover{background:#b02a37} |
| .quote-btn-print{background:var(--p);color:#fff}.quote-btn-print:hover{background:var(--pl)} |
| @media(max-width:768px){.quote-grid{grid-template-columns:1fr}.quote-modal{border-radius:12px}.quote-table{font-size:.7rem}} |
|
|
|
|
| .checkout-done-btn{display:none !important}.vas-unlocked .checkout-done-btn{display:flex !important} |
| .chat-msg.bot{max-height:58vh;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin}.chat-msg.bot::-webkit-scrollbar{width:5px}.chat-msg.bot::-webkit-scrollbar-thumb{background:#94a3b8;border-radius:8px} |
| </style> |
| |
| <script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> |
| </head> |
| <body> |
|
|
| <div class="topbar"><div class="container"> |
| <div class="topbar-item"><i class="fas fa-truck-fast"></i> Miễn phí vận chuyển</div> |
| <div class="topbar-item"><i class="fas fa-shield-halved"></i> Bảo hành chính hãng</div> |
| <div class="topbar-item"><i class="fas fa-headset"></i> Hỗ trợ 24/7</div> |
|
|
| </div></div> |
|
|
| <header class="header"><div class="container"> |
| <div class="logo" onclick="goHome()"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" alt="V.AI STUDIO" style="height:44px;object-fit:contain"><div><div class="logo-name">V.AI <span>STUDIO</span></div><div class="logo-sub">Niềm tin khách hàng là tài sản của chúng tôi</div></div></div> |
| <div class="search-box"><i class="fas fa-search"></i><input type="text" id="q" placeholder="Tìm sản phẩm Malloca, Eurogold, Grob, Canzy & Demax..." oninput="doSearch()"><span class="search-count" id="sc"></span></div> |
| <div class="nav-tabs"> |
| <div class="nav-tab active" data-page="products" onclick="showPage('products')"><i class="fas fa-th-large"></i> Sản Phẩm</div> |
| <div class="nav-tab" data-page="catalogue" onclick="showPage('catalogue')"><i class="fas fa-book-open"></i> Catalogue</div> |
| <div class="nav-tab" data-page="contact" onclick="showPage('contact')"><i class="fas fa-envelope"></i> Liên Hệ</div> |
| </div> |
| <div class="nav-icons"> |
| <a href="#" onclick="toggleChat();return false" title="Tư Vấn AI" style="background:linear-gradient(135deg,var(--p),#0077b6);color:#fff"><i class="fas fa-robot"></i></a> |
| <a href="#" onclick="openCart();return false" title="Giỏ hàng" class="cart-badge"><i class="fas fa-shopping-cart"></i><span class="cart-num" id="cartNum" style="display:none">0</span></a> |
| </div> |
| <button class="hamburger" onclick="document.getElementById('msb').classList.toggle('open')"><span></span><span></span><span></span></button> |
| </div></header> |
|
|
| <section class="hero"><div class="container"> |
| <img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_600.png" alt="V.AI STUDIO" style="height:80px;object-fit:contain;margin-bottom:12px"> |
| <h1>V.AI <span>STUDIO</span></h1> |
| <p style="font-size:1rem;font-style:italic;color:rgba(255,255,255,.9);margin-bottom:6px">❝ Niềm tin khách hàng là tài sản của chúng tôi ❞</p> |
| <p>Thiết bị nhà bếp & khóa thông minh — Malloca, Eurogold, Grob, Canzy & Demax</p> |
| <div class="hero-stats" id="heroStats"> |
| <div class="hero-stat"><strong id="statTotal">1979</strong><span>Sản phẩm</span></div> |
| <div class="hero-stat"><strong id="statCats">17</strong><span>Danh mục</span></div> |
| <div class="hero-stat"><strong id="statBrands">8</strong><span>Đối tác</span></div> |
| <div class="hero-stat"><strong>100%</strong><span>Dữ liệu thật</span></div> |
| </div> |
| </div></section> |
| |
|
|
|
|
|
|
| |
| <div style="background:#fff;padding:16px 0;border-bottom:1px solid #e2e8f0"> |
| <div class="container" style="display:flex;gap:12px;flex-wrap:wrap;justify-content:center;max-width:900px"> |
| <div style="flex:1;min-width:280px;position:relative"> |
| <i class="fas fa-robot" style="position:absolute;left:12px;top:50%;transform:translateY(-50%);color:#003f62;font-size:.85rem"></i> |
| <input type="text" id="aiSearch" placeholder="🔍 AI tìm kiếm: bếp từ đôi dưới 15 triệu, máy hút mùi tốt nhất..." style="width:100%;padding:11px 14px 11px 36px;border:2px solid #003f62;border-radius:10px;font-size:.85rem;font-family:inherit;outline:none" onkeypress="if(event.key==='Enter')doAISearch()"> |
| </div> |
| <button onclick="doAISearch()" style="padding:11px 20px;background:#003f62;color:#fff;border:none;border-radius:10px;font-weight:700;font-size:.85rem;cursor:pointer;white-space:nowrap"><i class="fas fa-search"></i> Tìm AI</button> |
| </div> |
| <div id="aiResults" style="max-width:900px;margin:12px auto 0;display:none;background:#f8fafc;border-radius:10px;padding:14px;font-size:.82rem"></div> |
| </div> |
| |
| <div style="background:#fff;padding:14px 0;border-bottom:1px solid var(--gl)"> |
| <div class="container" style="display:flex;align-items:center;justify-content:center;gap:20px;flex-wrap:wrap"> |
| <span style="font-size:.68rem;font-weight:700;color:var(--g);text-transform:uppercase;letter-spacing:1px;white-space:nowrap">Đối tác:</span> |
| <a href="javascript:void(0)" onclick="setBrand('Malloca')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/malloca.png" alt="Malloca" style="height:28px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Eurogold')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/eurogold.png" alt="Eurogold" style="height:26px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Grob')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/grob.webp" alt="Grob" style="height:24px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Canzy')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/canzy.png" alt="Canzy" style="height:26px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Demax')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/demax.png" alt="Demax" style="height:22px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Hafele')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/hafele.png" alt="Hafele" style="height:22px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Garis')" style="display:flex;align-items:center"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/garis.png" alt="Garis" style="height:24px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"></a> |
| <a href="javascript:void(0)" onclick="setBrand('Boss')" style="display:flex;align-items:center"><span style="font-size:.72rem;color:#333;font-weight:700;background:#f0f0f0;padding:3px 8px;border-radius:4px;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'">BOSS</span></a> |
| <a href="javascript:void(0)" onclick="setBrand('_dmx')" style="display:flex;align-items:center;gap:6px"> |
| <img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/dmx.png" alt="Điện Máy Xanh" style="height:24px;object-fit:contain;opacity:.75;transition:.3s" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='.75'"> |
| <span style="font-size:.56rem;color:#e53935;font-weight:700;background:#fff3f3;padding:2px 6px;border-radius:4px;white-space:nowrap">Cam kết giá rẻ hơn ĐMX</span> |
| </a> |
| </div> |
| </div> |
|
|
| <div class="container"> |
| |
| <div id="page-products" class="page-section active"><div id="listView"> |
| <div class="main"> |
| <aside class="sidebar" id="sidebar"> |
| <div class="sb-card"><h3><i class="fas fa-building"></i> Thương Hiệu</h3><div class="pr" id="br"></div></div> |
| <div class="sb-card"><h3><i class="fas fa-layer-group"></i> Danh Mục</h3><ul class="cat-list" id="cl"></ul></div> |
| <div class="sb-card"><h3><i class="fas fa-tag"></i> Khoảng Giá</h3><div class="pr" id="pr"></div></div> |
| </aside> |
| <main class="content"> |
| <div class="toolbar"> |
| <span class="result-count" id="rc"></span> |
| <div style="display:flex;gap:7px;align-items:center"> |
| <select class="sort-select" id="ss" onchange="doSort()"><option value="default">Mặc định</option><option value="pa">Giá: Thấp → Cao</option><option value="pd">Giá: Cao → Thấp</option><option value="na">Tên: A → Z</option></select> |
| <div class="view-btns"><button class="view-btn active" id="gb" onclick="setV('g')"><i class="fas fa-th-large"></i></button><button class="view-btn" id="lb" onclick="setV('l')"><i class="fas fa-list"></i></button></div> |
| </div> |
| </div> |
| <div class="pg" id="grid"></div> |
| <div class="pagination" id="pag"></div> |
| <div id="catSearchResults"></div> |
| </main> |
| </div> |
| </div> |
|
|
| <div id="detailView"></div> |
| </div> |
|
|
| |
| <div class="page-section" id="page-catalogue"> |
| <div class="section-header" style="text-align:center;margin-bottom:10px"> |
| <h2 style="font-size:1.8rem;font-weight:800;color:var(--d)"><i class="fas fa-book-open" style="color:var(--a);margin-right:10px"></i>Catalogue <span style="color:var(--a)">Sản phẩm</span></h2> |
| <p style="color:var(--g);font-size:.9rem;max-width:550px;margin:10px auto 0">10 bộ catalogue — tìm kiếm theo văn bản trong ảnh</p> |
| <div style="max-width:480px;margin:16px auto 0;position:relative"> |
| <i class="fas fa-search" style="position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--g);font-size:.85rem"></i> |
| <input type="text" id="catSearch" placeholder="Tìm trong catalogue... (tên SP, mã SP, thông số...)" oninput="searchCatalogue()" style="width:100%;padding:10px 14px 10px 40px;border:2px solid var(--gl);border-radius:12px;font-size:.85rem;font-family:inherit;outline:none"> |
| <span id="catSearchCount" style="position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:.68rem;color:var(--g);background:var(--l);padding:2px 7px;border-radius:6px"></span> |
| </div> |
| </div> |
| |
| <div id="catTabs"></div> |
| |
| <div id="catContent"></div> |
| </div> |
|
|
| |
| <div class="page-section" id="page-contact"> |
| <div class="section-header" style="text-align:center;margin-bottom:10px"> |
| <h2 style="font-size:1.8rem;font-weight:800;color:var(--d)"><i class="fas fa-envelope" style="color:var(--a);margin-right:10px"></i>Liên Hệ <span style="color:var(--a)">Malloca</span></h2> |
| <p style="color:var(--g);font-size:.9rem;max-width:500px;margin:10px auto 0">Liên hệ trực tiếp hoặc gửi tin nhắn — chúng tôi sẵn sàng hỗ trợ bạn</p> |
| </div> |
| <div class="contact-grid"> |
| <div class="contact-card"> |
| <h3><i class="fas fa-building"></i> Thông Tin Liên Hệ</h3> |
| <ul class="contact-info"> |
| <li><i class="fas fa-map-marker-alt"></i><div><strong>Showroom Hà Nội</strong><br>10 Chương Dương Độ, Hoàn Kiếm, Hà Nội</div></li> |
| <li><i class="fas fa-comment-dots"></i><div><strong>Zalo tư vấn</strong><br><a href="https://zalo.me/0981873395" target="_blank" style="color:var(--p);font-weight:700">0981 873 395</a> · <a href="https://zalo.me/0918258385" target="_blank" style="color:var(--p);font-weight:700">0918 258 385</a></div></li> |
| <li><i class="fas fa-qrcode"></i><div><strong>QR Kết bạn Zalo</strong><br><img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://zalo.me/0981873395" alt="QR Zalo 0981873395" style="width:120px;border-radius:8px;margin-top:6px;border:2px solid var(--gl)"><br><span style="font-size:.72rem;color:var(--g)">Quét mã để kết bạn Zalo</span></div></li> |
| <li><i class="fas fa-envelope"></i><div><strong>Email</strong><br><a href="mailto:info@malloca.com" style="color:var(--p)">info@malloca.com</a></div></li> |
| <li><i class="fas fa-clock"></i><div><strong>Giờ làm việc</strong><br>Thứ 2 - Thứ 7: 8:00 - 17:30<br>Chủ nhật: 9:00 - 12:00</div></li> |
| <li><i class="fas fa-robot"></i><div><strong>Tư Vấn AI</strong><br><a href="javascript:void(0)" onclick="toggleChat()" style="color:var(--p);font-weight:700">Chat ngay →</a></div></li> |
| </ul> |
| <div style="display:flex;gap:8px;margin-top:16px"> |
| <a href="https://zalo.me/0981873395" target="_blank" style="padding:10px 16px;background:#0068FF;color:#fff;border-radius:10px;font-size:.8rem;font-weight:600;display:flex;align-items:center;gap:6px"><i class="fas fa-comment-dots"></i> Zalo 1</a> |
| <a href="https://zalo.me/0918258385" target="_blank" style="padding:10px 16px;background:#0068FF;color:#fff;border-radius:10px;font-size:.8rem;font-weight:600;display:flex;align-items:center;gap:6px"><i class="fas fa-comment-dots"></i> Zalo 2</a> |
| </div> |
| </div> |
| <div class="contact-card"> |
| <h3><i class="fas fa-paper-plane"></i> Gửi Tin Nhắn</h3> |
| <form onsubmit="submitContact(event)"> |
| <div class="form-group"><label>Họ tên *</label><input class="form-input" id="ctName" placeholder="Nhập họ tên" required></div> |
| <div class="form-group"><label>Email *</label><input class="form-input" id="ctEmail" type="email" placeholder="email@example.com" required></div> |
| <div class="form-group"><label>Số điện thoại</label><input class="form-input" id="ctPhone" placeholder="Nhập SĐT"></div> |
| <div class="form-group"><label>Nội dung *</label><textarea class="form-input" id="ctMsg" placeholder="Nhập nội dung liên hệ..." required></textarea></div> |
| <button type="submit" class="form-btn"><i class="fas fa-paper-plane"></i> Gửi Tin Nhắn</button> |
| </form> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="msb" id="msb"> |
| <div class="msb-h"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" alt="V.AI STUDIO" style="height:36px;object-fit:contain"><button class="msb-close" onclick="this.closest('.msb').classList.remove('open')"><i class="fas fa-times"></i></button></div> |
| <div style="display:flex;flex-direction:column;gap:4px;margin-bottom:16px;padding-bottom:16px;border-bottom:2px solid var(--gl)"> |
| <div style="padding:12px;border-radius:10px;font-weight:600;cursor:pointer;font-size:.9rem" onclick="closeMsb();showPage('products')"><i class="fas fa-th-large" style="margin-right:8px;color:var(--a)"></i> Sản Phẩm</div> |
| <div style="padding:12px;border-radius:10px;font-weight:600;cursor:pointer;font-size:.9rem" onclick="closeMsb();showPage('catalogue')"><i class="fas fa-book-open" style="margin-right:8px;color:var(--a)"></i> Catalogue</div> |
| <div style="padding:12px;border-radius:10px;font-weight:600;cursor:pointer;font-size:.9rem" onclick="closeMsb();showPage('contact')"><i class="fas fa-envelope" style="margin-right:8px;color:var(--a)"></i> Liên Hệ</div> |
| <div style="padding:12px;border-radius:10px;font-weight:600;cursor:pointer;font-size:.9rem" onclick="closeMsb();openCart()"><i class="fas fa-shopping-cart" style="margin-right:8px;color:var(--a)"></i> Giỏ Hàng</div> |
| </div> |
| <div class="sb-card"><h3><i class="fas fa-building"></i> Thương Hiệu</h3><div class="pr" id="brm"></div></div> |
| <div class="sb-card"><h3><i class="fas fa-layer-group"></i> Danh Mục</h3><ul class="cat-list" id="clm"></ul></div> |
| <div class="sb-card"><h3><i class="fas fa-tag"></i> Khoảng Giá</h3><div class="pr" id="prm"></div></div> |
| </div> |
|
|
|
|
| |
| <div class="cart-overlay" id="cartOverlay" onclick="closeCart()"></div> |
| <div class="cart-drawer" id="cartDrawer"> |
| <div class="cart-header"><h3><i class="fas fa-shopping-cart"></i> Giỏ Hàng</h3><button class="cart-close" onclick="closeCart()"><i class="fas fa-times"></i></button></div> |
| <div class="cart-items" id="cartItems"></div> |
| <div class="cart-footer" id="cartFooter" style="display:none"> |
| <div class="cart-total"><span>Tổng cộng:</span><span id="cartTotal">0đ</span></div> |
| <div class="cart-footer-btns"> |
| <button class="cart-checkout-btn" onclick="openCheckout()"><i class="fas fa-qrcode"></i> Thanh toán</button> |
| <button class="cart-checkout-btn" onclick="openQuotation()" style="background:var(--p)"><i class="fas fa-file-invoice"></i> Báo giá</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="checkout-overlay" id="checkoutOverlay" onclick="if(event.target===this)closeCheckout()"> |
| <div class="checkout-modal"> |
| <button class="checkout-close" onclick="closeCheckout()"><i class="fas fa-times"></i></button> |
| <div id="checkoutContent"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="quote-overlay" id="quoteOverlay" onclick="if(event.target===this)closeQuotation()"> |
| <div class="quote-modal"> |
| <button class="quote-close" onclick="closeQuotation()"><i class="fas fa-times"></i></button> |
| <div class="quote-head"> |
| <h2><i class="fas fa-file-invoice" style="margin-right:8px"></i>Lập Báo Giá</h2> |
| <p>Điền thông tin khách hàng và chiết khấu để xuất báo giá</p> |
| </div> |
| <div class="quote-body"> |
|
|
| <div class="quote-section"> |
| <h3><i class="fas fa-user"></i> Thông tin khách hàng</h3> |
| <div class="quote-grid"> |
| <div class="quote-field"><label>Họ tên khách hàng</label><input id="qcName" placeholder="Nhập họ tên"></div> |
| <div class="quote-field"><label>Số điện thoại</label><input id="qcPhone" placeholder="Nhập SĐT"></div> |
| <div class="quote-field"><label>Email</label><input id="qcEmail" placeholder="Nhập email"></div> |
| <div class="quote-field"><label>Địa chỉ</label><input id="qcAddr" placeholder="Nhập địa chỉ"></div> |
| <div class="quote-field"><label>Mã đơn hàng</label><input id="qcCompany" placeholder="Tự động tạo" readonly style="background:var(--l);font-weight:700;color:var(--p)"></div> |
| <div class="quote-field"><label>Ngày báo giá</label><input id="qcDate" type="date"></div> |
| </div> |
| <div style="margin-top:12px;padding:12px 14px;background:var(--l);border-radius:10px"> |
| <div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"> |
| <i class="fas fa-lock" id="qcLockIcon" style="color:var(--g);font-size:.85rem"></i> |
| <input id="qcAccessCode" type="password" placeholder="Nhập mã truy cập..." style="width:160px;padding:7px 10px;border:1.5px solid var(--gl);border-radius:8px;font-size:.8rem;font-family:inherit;outline:none;background:#fff" oninput="checkQuoteAccess()"> |
| <span id="qcAccessStatus" style="font-size:.72rem;font-weight:600;color:var(--g)">🔒</span> |
| </div> |
| <div id="aiPromptBox" style="display:none"> |
| <div style="display:flex;gap:8px"> |
| <textarea id="qcAiPrompt" placeholder="VD: Malloca ck 35%, giao hàng 200k, lắp đặt 500k, cọc 5tr, ghi chú: giao thứ 7" style="flex:1;padding:8px 10px;border:1.5px solid var(--p);border-radius:8px;font-size:.82rem;font-family:inherit;outline:none;resize:none;height:48px;background:#fff"></textarea> |
| <button onclick="applyAiDiscount()" style="padding:8px 16px;background:var(--p);color:#fff;border:none;border-radius:8px;font-size:.8rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;display:flex;align-items:center;gap:6px" id="aiDiscBtn"><i class="fas fa-robot"></i> Áp dụng</button> |
| </div> |
| <div id="aiDiscStatus" style="font-size:.72rem;color:var(--g);margin-top:4px"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="quote-section"> |
| <h3><i class="fas fa-list-check"></i> Chi tiết sản phẩm</h3> |
| <div style="overflow-x:auto"> |
| <table class="quote-table" id="quoteTable"> |
| <thead><tr> |
| <th>STT</th><th>Hình ảnh</th><th style="text-align:left">Tên sản phẩm</th><th>Mã SP</th><th>Thông tin</th><th>SL</th><th>Đơn giá</th><th>Đơn giá CK</th><th>Thành tiền</th><th>Ghi chú</th> |
| </tr></thead> |
| <tbody id="quoteTableBody"></tbody> |
| <tfoot><tr class="quote-total-row"><td colspan="8" style="text-align:right;padding-right:12px">TỔNG CỘNG:</td><td class="qt-price" id="quoteTotalCell">0đ</td><td></td></tr></tfoot> |
| </table> |
| </div> |
| </div> |
|
|
| <div class="quote-actions"> |
| <button class="quote-btn" onclick="shareQuoteImage()" style="background:#25D366"><i class="fas fa-share-alt"></i> Chia sẻ ảnh</button> |
| <button class="quote-btn" onclick="shareQuoteLink()" style="background:#0068FF"><i class="fas fa-share-alt"></i> Chia sẻ báo giá</button> |
| <button class="quote-btn quote-btn-excel" onclick="exportExcel()"><i class="fas fa-file-excel"></i> Xuất Excel</button> |
| <button class="quote-btn quote-btn-pdf" onclick="exportPDF()"><i class="fas fa-file-pdf"></i> Xuất PDF</button> |
| <button class="quote-btn quote-btn-print" onclick="printQuotation()"><i class="fas fa-print"></i> In báo giá</button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <footer class="footer"><div class="container"> |
| <div class="footer-grid"> |
| <div class="footer-brand"><img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" alt="V.AI STUDIO" style="height:50px;object-fit:contain;margin-bottom:8px;filter:brightness(10)"><div class="logo-name" style="color:#fff;font-size:1.1rem">V.AI <span style="color:var(--a)">STUDIO</span></div><p style="font-style:italic;opacity:.85;margin-bottom:4px">❝ Niềm tin khách hàng là tài sản của chúng tôi ❞</p><p>Thiết bị nhà bếp & khóa thông minh — Malloca, Eurogold, Grob, Canzy & Demax.</p> |
| <div class="social-links"><a href="https://zalo.me/0981873395" target="_blank" title="Zalo"><i class="fas fa-comment-dots"></i></a><a href="https://zalo.me/0918258385" target="_blank" title="Zalo 2"><i class="fas fa-comments"></i></a></div></div> |
| <div><h4>Thiết Bị Nhà Bếp</h4><ul class="footer-links"><li><a href="javascript:void(0)" onclick="setCat('may-hut-khoi-khu-mui')">Máy hút khói khử mùi</a></li><li><a href="javascript:void(0)" onclick="setCat('bep-dien-tu-bep-gas')">Bếp điện từ, bếp gas</a></li><li><a href="javascript:void(0)" onclick="setCat('lo-nuong')">Lò nướng, lò hấp</a></li><li><a href="javascript:void(0)" onclick="setCat('chau-rua-chen')">Chậu rửa chén</a></li><li><a href="javascript:void(0)" onclick="setCat('voi-rua-chen')">Vòi rửa chén</a></li></ul></div> |
| <div><h4>Thêm</h4><ul class="footer-links"><li><a href="javascript:void(0)" onclick="setCat('tu-lanh-tu-ruou')">Tủ lạnh, tủ rượu</a></li><li><a href="javascript:void(0)" onclick="setCat('may-rua-chen-may-say-chen')">Máy rửa chén</a></li><li><a href="javascript:void(0)" onclick="setCat('thiet-bi-gia-dung-nho')">Gia dụng nhỏ</a></li><li><a href="javascript:void(0)" onclick="showPage('catalogue')">Catalogue</a></li><li><a href="javascript:void(0)" onclick="toggleChat()">🤖 Tư Vấn AI</a></li></ul></div> |
| <div><h4>Hỗ Trợ</h4><ul class="footer-links"><li><a href="javascript:void(0)" onclick="toggleChat()">Tư vấn sản phẩm</a></li><li><a href="javascript:void(0)" onclick="showPage('contact')">Liên hệ</a></li><li><a href="https://zalo.me/0981873395" target="_blank">Zalo: 0981 873 395</a></li><li><a href="javascript:void(0)" onclick="showPage('catalogue')">Xem catalogue</a></li><li><a href="javascript:void(0)" onclick="goHome()">Trang chủ</a></li></ul></div> |
| </div> |
| <div class="footer-bottom"><p>© 2026 V.AI STUDIO • Thiết bị nhà bếp & khóa thông minh — Malloca, Eurogold, Grob, Canzy, Demax, Hafele, Garis & Điện Máy Xanh</p><p style="margin-top:6px;font-size:.65rem;color:rgba(255,255,255,.45)">Tất cả sản phẩm đã được cập nhật giá mới nhất nhanh nhất 2026 — Có sự hỗ trợ từ V.AI STUDIO</p></div> |
| </div></footer> |
|
|
| <script> |
| let D=[],S={cat:'all',q:'',pmin:0,pmax:1e15,sort:'default',pg:1,view:'g',brand:'all'}; |
| const PP=24; |
| const PRICES=[ |
| {l:'Tất cả',min:0,max:1e15},{l:'Dưới 5 triệu',min:0,max:5e6},{l:'5-15 triệu',min:5e6,max:15e6}, |
| {l:'15-30 triệu',min:15e6,max:3e7},{l:'30-50 triệu',min:3e7,max:5e7},{l:'Trên 50 triệu',min:5e7,max:1e15} |
| ]; |
| |
| Promise.all(['products_with_slugs.json','products_garis_slugs.json','products_dmx_slugs.json','products_boss_slugs.json'].map(f=>fetch(f).then(r=>r.ok?r.json():[]).catch(()=>[]))).then(all=>{let d=[].concat(...all);D=d.map(p=>{let o={name:p.n,link:p.l,image:p.i,price:p.p,priceNum:p.pn,cat:p.c,catSlug:p.cs,catIcon:p.ci,subCat:p.sub_c||'',subCatSlug:p.sub_cs||'',images:p.imgs||[],summary:p.sum||'',desc:p.desc||'',specs:p.specs||{},feats:p.feats||[],sku:p.sku||'',video:p.vid||'',model:p.mod||'',slug:p.slug||'',brand:p.brand||(p.n.includes('| Demax |')?'Demax':p.n.includes('| Canzy |')?'Canzy':p.n.includes('| Grob |')?'Grob':p.n.includes('| Eurogold |')||p.n.startsWith('Eurogold |')?'Eurogold':'Malloca'),_source:p._source||'',_sourceUrl:p._sourceUrl||''};o._idx=buildSearchIndex(o);return o}).filter(p=>{let isExt=(p.link&&(p.link.indexOf('dienmayxanh')!==-1||p.link.indexOf('hafele-vn')!==-1))||(p._source&&(p._source.toLowerCase().indexOf('hafele')!==-1||p._source.toLowerCase().indexOf('điện máy xanh')!==-1));if(!isExt)return true;let nm=((p.name||'')+(p.brand||'')+(p.sku||'')).toLowerCase();return nm.indexOf('malloca')===-1;});init()}).catch(e=>{document.getElementById('grid').innerHTML='<div class="empty" style="grid-column:1/-1"><i class="fas fa-exclamation-triangle"></i><h3>Lỗi tải dữ liệu</h3><p>'+e.message+'</p><button class="reset-btn" onclick="location.reload()">Tải lại</button></div>'}); |
| |
| function nVN(s){return s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d').replace(/Đ/g,'d')} |
| |
| // Synonym map (normalized, no diacritics) |
| const SYN={'ke':['gia'],'gia':['ke'],'bat':['chen'],'chen':['bat'],'xoong':['noi'],'noi':['xoong'],'dao':['thot'],'thot':['dao'],'ray':['truot'],'truot':['ray'],'khoa':['lock'],'lock':['khoa'],'bon':['chau'],'chau':['bon'],'ro':['gia','ke']}; |
| |
| function smartSearch(query,text){ |
| // Strip . - space from query for SKU matching |
| let cleanQ=query.replace(/[.\- ]/g,''); |
| let cleanT=text.replace(/[.\- ]/g,''); |
| // 1. Exact substring on cleaned versions |
| if(cleanT.includes(cleanQ))return true; |
| if(text.includes(query))return true; |
| // 2. Split into words, ALL must match (any order) |
| let words=query.split(/\s+/).filter(w=>w); |
| if(!words.length)return true; |
| let matched=0; |
| for(let w of words){ |
| let cw=w.replace(/[.\-]/g,''); |
| if(text.includes(w)||cleanT.includes(cw)){matched++;continue;} |
| // Try synonym |
| let hit=false; |
| if(SYN[w]){for(let s of SYN[w]){if(text.includes(s)){hit=true;break;}}} |
| if(hit){matched++;continue;} |
| } |
| return matched===words.length; |
| } |
| function buildSearchIndex(p){ |
| let parts=[p.name,p.sku,p.model,p.brand,p.price]; |
| // Add cleaned SKU/model (no . - space) for search |
| if(p.sku)parts.push(p.sku.replace(/[.\- ]/g,'')); |
| if(p.model)parts.push(p.model.replace(/[.\- ]/g,'')); |
| if(p.feats&&p.feats.length)parts.push(p.feats.join(' ')); |
| if(p.specs){Object.entries(p.specs).forEach(([k,v])=>{parts.push(k);parts.push(String(v))})} |
| // AI semantic keywords based on product name |
| let n=p.name.toLowerCase(); |
| if(n.includes('giá bát')||n.includes('kệ bát'))parts.push('ke bat gia bat ro bat dia chen'); |
| if(n.includes('gia vị')||n.includes('chai lọ'))parts.push('ke gia vi chai lo'); |
| if(n.includes('xoong')||n.includes('nồi'))parts.push('xoong noi chao'); |
| if(n.includes('dao thớt'))parts.push('ke dao thot'); |
| if(n.includes('tẩy rửa'))parts.push('ke tay rua gia chai lo nuoc rua'); |
| if(n.includes('nâng hạ'))parts.push('nang ha len xuong'); |
| if(n.includes('cảm ứng'))parts.push('cam ung dien thong minh'); |
| if(n.includes('mâm xoay'))parts.push('mam xoay goc lien hoan'); |
| if(n.includes('liên hoàn'))parts.push('lien hoan goc mam xoay'); |
| if(n.includes('tủ kho')||n.includes('đồ khô'))parts.push('tu kho ke do kho'); |
| if(n.includes('khay chia'))parts.push('khay chia thia nia muong dia ngan keo'); |
| if(n.includes('thùng gạo'))parts.push('thung gao hop dung gao'); |
| if(n.includes('thùng rác'))parts.push('thung rac'); |
| if(n.includes('hút mùi'))parts.push('hut mui hut khoi khu mui'); |
| if(n.includes('bếp từ'))parts.push('bep tu bep dien tu'); |
| if(n.includes('bếp gas'))parts.push('bep gas'); |
| if(n.includes('khóa'))parts.push('khoa cua thong minh van tay face id'); |
| if(n.includes('chậu rửa'))parts.push('chau rua bon rua'); |
| if(n.includes('vòi rửa'))parts.push('voi rua nong lanh'); |
| if(n.includes('lò nướng'))parts.push('lo nuong'); |
| if(n.includes('lò vi'))parts.push('lo vi song'); |
| if(n.includes('máy rửa'))parts.push('may rua bat chen'); |
| if(n.includes('trang sức'))parts.push('trang suc ngan keo'); |
| if(n.includes('giày'))parts.push('giay ke de giay'); |
| if(n.includes('gương'))parts.push('guong xoay'); |
| if(n.includes('ray')||n.includes('bản lề'))parts.push('ray truot ban le giam chan'); |
| if(n.includes('lò nướng'))parts.push('lo nuong am tu nong'); |
| if(n.includes('lò vi'))parts.push('lo vi song am tu'); |
| if(n.includes('tủ áo')||n.includes('quần áo'))parts.push('tu ao phu kien tu quan ao'); |
| if(n.includes('trang sức'))parts.push('trang suc ngan keo de do'); |
| if(n.includes('giày'))parts.push('giay ke de giay tu ao'); |
| if(n.includes('gương'))parts.push('guong xoay tu ao thong minh'); |
| if(n.includes('máy rửa'))parts.push('may rua bat chen doc lap am tu'); |
| return nVN(parts.join(' ')); |
| } |
| |
| // init() is defined at the end of the script with routing logic |
| |
| function buildCats(){ |
| let cats={}; |
| let subs={}; |
| D.forEach(p=>{ |
| if(!cats[p.catSlug])cats[p.catSlug]={n:p.cat,i:p.catIcon,c:0,hasSub:false}; |
| cats[p.catSlug].c++; |
| if(p.subCatSlug){ |
| cats[p.catSlug].hasSub=true; |
| if(!subs[p.subCatSlug])subs[p.subCatSlug]={n:p.subCat,parent:p.catSlug,c:0}; |
| subs[p.subCatSlug].c++; |
| } |
| }); |
| let h=`<li class="cat-item active" data-s="all" onclick="setCat('all')"><i class="fas fa-border-all"></i>Tất cả<span class="cat-count">${D.length}</span></li>`; |
| Object.entries(cats).forEach(([s,c])=>{ |
| h+=`<li class="cat-item" data-s="${s}" onclick="setCat('${s}')"><i class="fas ${c.i}"></i>${c.n}<span class="cat-count">${c.c}</span></li>`; |
| if(c.hasSub){ |
| let subList=Object.entries(subs).filter(([sk,sv])=>sv.parent===s).sort((a,b)=>b[1].c-a[1].c); |
| subList.forEach(([sk,sv])=>{ |
| h+=`<li class="cat-item cat-sub" data-s="${sk}" onclick="setCat('${sk}')"><span style="margin-left:12px;font-size:.7rem;opacity:.5">└</span>${sv.n}<span class="cat-count">${sv.c}</span></li>`; |
| }); |
| } |
| }); |
| document.getElementById('cl').innerHTML=h;document.getElementById('clm').innerHTML=h; |
| } |
| |
| function buildPrices(){ |
| let h='';PRICES.forEach((p,i)=>{h+=`<div class="pr-item${i===0?' active':''}" data-i="${i}" onclick="setPrice(${p.min},${p.max},${i})"><span class="pr-dot"></span>${p.l}</div>`}); |
| document.getElementById('pr').innerHTML=h;document.getElementById('prm').innerHTML=h; |
| } |
| function buildBrands(){ |
| let brands={};D.forEach(p=>{brands[p.brand]=(brands[p.brand]||0)+1}); |
| let h=`<div class="pr-item active" data-b="all" onclick="setBrand('all')"><span class="pr-dot"></span>Tất cả (${D.length})</div>`; |
| Object.entries(brands).sort((a,b)=>b[1]-a[1]).forEach(([b,c])=>{h+=`<div class="pr-item" data-b="${b}" onclick="setBrand('${b}')"><span class="pr-dot"></span>${b} (${c})</div>`}); |
| document.getElementById('br').innerHTML=h;document.getElementById('brm').innerHTML=h; |
| document.getElementById('statTotal').textContent=D.length; |
| {let _c={};D.forEach(p=>{_c[p.catSlug]=1});document.getElementById('statCats').textContent=Object.keys(_c).length;} |
| {let _partners=['Malloca','Eurogold','Grob','Canzy','Demax','Hafele','Garis','Điện Máy Xanh'];let _found={};D.forEach(p=>{let b=p.brand||'';_partners.forEach(pp=>{if(b===pp||b.toLowerCase().includes(pp.toLowerCase()))_found[pp]=1})});let bEl=document.getElementById('statBrands');if(bEl)bEl.textContent=Object.keys(_found).length;} |
| } |
| |
| |
| |
| function getF(){ |
| let r=D; |
| if(S.brand==='_dmx')r=r.filter(p=>p.slug&&p.slug.startsWith('dmx-')); |
| else if(S.brand!=='all')r=r.filter(p=>p.brand===S.brand); |
| if(S.cat!=='all')r=r.filter(p=>p.catSlug===S.cat||p.subCatSlug===S.cat); |
| if(S.q){let q=nVN(S.q);r=r.filter(p=>smartSearch(q,p._idx))} |
| if(S.pmin>0||S.pmax<1e15)r=r.filter(p=>p.priceNum>=S.pmin&&p.priceNum<=S.pmax); |
| if(S.sort==='pa')r.sort((a,b)=>a.priceNum-b.priceNum); |
| else if(S.sort==='pd')r.sort((a,b)=>b.priceNum-a.priceNum); |
| else if(S.sort==='na')r.sort((a,b)=>a.name.localeCompare(b.name,'vi')); |
| return r; |
| } |
| |
| function render(){ |
| let f=getF(),tp=Math.ceil(f.length/PP);if(S.pg>tp)S.pg=tp||1; |
| let s=(S.pg-1)*PP,items=f.slice(s,s+PP); |
| document.getElementById('rc').innerHTML=`<strong>${f.length}</strong> sản phẩm`; |
| document.getElementById('sc').textContent=S.q?f.length+' kết quả':''; |
| let g=document.getElementById('grid'); |
| g.className='pg'+(S.view==='l'?' lv':''); |
| if(!items.length){g.innerHTML=`<div class="empty" style="grid-column:1/-1"><i class="fas fa-search"></i><h3>Không tìm thấy</h3><p>Thử thay đổi bộ lọc hoặc từ khóa</p><button class="reset-btn" onclick="resetAll()">Xóa bộ lọc</button></div>`; |
| }else{ |
| g.innerHTML=items.map((p,i)=>{let idx=D.indexOf(p);let brandColor=p.brand==='Eurogold'?'#c8102e':p.brand==='Grob'?'#2e7d32':'var(--p)';let href='/san-pham/'+p.slug+'/index.html';return`<a class="pc fade" href="${href}" onclick="event.preventDefault();showDetail(${idx})" style="transition-delay:${Math.min(i*25,400)}ms;display:block"><div class="pi"><img src="${p.image}" alt="${p.name}" loading="lazy" onerror="if(!this.dataset.retry){this.dataset.retry=1;this.referrerPolicy='no-referrer';this.src=this.src}else{this.style.display='none'}"><span class="pi-badge"><i class="fas ${p.catIcon}"></i> ${p.cat}</span><span style="position:absolute;top:8px;right:8px;background:${brandColor};color:#fff;padding:3px 8px;border-radius:5px;font-size:.58rem;font-weight:700">${p.brand}</span></div><div class="pb"><div style="font-size:.65rem;font-weight:700;color:${brandColor};letter-spacing:.5px;margin-bottom:4px">${p.model||p.brand}</div><div class="pn">${p.name}</div><div class="pf"><span class="pp">${p.price||'Liên hệ'}</span><i class="fas fa-chevron-right"></i></div></div></a>`}).join(''); |
| } |
| requestAnimationFrame(()=>document.querySelectorAll('.fade').forEach(e=>e.classList.add('vis'))); |
| renderPag(tp); |
| } |
| |
| function renderPag(tp){ |
| if(tp<=1){document.getElementById('pag').innerHTML='';return} |
| let h=`<button class="pgb" onclick="goPg(${S.pg-1})"${S.pg===1?' disabled':''}><i class="fas fa-chevron-left"></i></button>`; |
| let pages=[]; |
| if(tp<=7){for(let i=1;i<=tp;i++)pages.push(i)} |
| else{pages.push(1);if(S.pg>3)pages.push('.');for(let i=Math.max(2,S.pg-1);i<=Math.min(tp-1,S.pg+1);i++)pages.push(i);if(S.pg<tp-2)pages.push('.');pages.push(tp)} |
| pages.forEach(p=>{if(p==='.')h+=`<span class="pg-dots">...</span>`;else h+=`<button class="pgb${p===S.pg?' active':''}" onclick="goPg(${p})">${p}</button>`}); |
| h+=`<button class="pgb" onclick="goPg(${S.pg+1})"${S.pg===tp?' disabled':''}><i class="fas fa-chevron-right"></i></button>`; |
| document.getElementById('pag').innerHTML=h; |
| } |
| |
| // ===== DETAIL VIEW ===== |
| let _skipHistory=false; |
| function showDetail(idx){ |
| let p=D[idx]; |
| // Update URL with SEO-friendly slug using History API |
| if(!_skipHistory){ |
| let slugUrl='/san-pham/'+p.slug+'/index.html'; |
| history.pushState({type:'product',idx:idx},'',slugUrl); |
| } |
| _skipHistory=false; |
| document.title=p.name+' | '+p.brand+' - V.AI STUDIO'; |
| // Update OG meta for sharing |
| var ogImg=p.image||(p.images&&p.images[0])||''; |
| if(ogImg.startsWith('//'))ogImg='https:'+ogImg; |
| var pUrl=location.origin+'/san-pham/'+p.slug+'/index.html'; |
| var el; |
| if(el=document.getElementById('ogTitle'))el.content=p.name+' | V.AI STUDIO'; |
| if(el=document.getElementById('ogDesc'))el.content=(p.summary||p.desc||p.name).substring(0,200); |
| if(el=document.getElementById('ogImage'))el.content=ogImg; |
| if(el=document.getElementById('ogUrl'))el.content=pUrl; |
| if(el=document.getElementById('twTitle'))el.content=p.name+' | V.AI STUDIO'; |
| if(el=document.getElementById('twDesc'))el.content=(p.summary||p.desc||p.name).substring(0,200); |
| if(el=document.getElementById('twImage'))el.content=ogImg; |
| document.getElementById('listView').style.display='none'; |
| document.querySelector('.hero').style.display='none'; |
| let dv=document.getElementById('detailView'); |
| dv.classList.add('show'); |
| |
| // Build gallery carousel |
| let allImgs=p.images.length?p.images:[p.image]; |
| let slides=allImgs.map((img,i)=>`<div class="gallery-slide"><img src="${img}" alt="${p.name}" loading="${i===0?'eager':'lazy'}"></div>`).join(''); |
| let dots=allImgs.length>1?`<div class="gallery-dots">${allImgs.map((_,i)=>`<div class="gallery-dot${i===0?' active':''}" onclick="goSlide(${i})"></div>`).join('')}</div>`:''; |
| let thumbs=allImgs.length>1?allImgs.map((img,i)=>`<div class="gallery-thumb${i===0?' active':''}" onclick="goSlide(${i})"><img src="${img}" alt="" loading="lazy"></div>`).join(''):''; |
| |
| // Specs table |
| let specsHtml=''; |
| if(Object.keys(p.specs).length){ |
| specsHtml='<table class="specs-table">'+Object.entries(p.specs).map(([k,v])=>`<tr><td>${k}</td><td>${v}</td></tr>`).join('')+'</table>'; |
| }else{specsHtml='<p style="color:var(--g);font-size:.88rem">Chưa có thông số kỹ thuật chi tiết.</p>'} |
| |
| // Features |
| let featsHtml=''; |
| if(p.feats.length){featsHtml='<ul class="feat-list">'+p.feats.map(f=>`<li class="feat-item"><i class="fas fa-check-circle"></i><span>${f}</span></li>`).join('')+'</ul>'} |
| else{featsHtml='<p style="color:var(--g);font-size:.88rem">Chưa có danh sách tính năng.</p>'} |
| |
| // Description |
| let descHtml=p.desc?`<div class="desc-text">${p.desc.replace(/\n/g,'<br>')}</div>`:'<p style="color:var(--g);font-size:.88rem">Chưa có mô tả chi tiết.</p>'; |
| |
| // Video |
| let videoHtml=p.video?`<div class="video-wrap"><iframe src="${p.video}" allowfullscreen loading="lazy"></iframe></div>`:'<p style="color:var(--g);font-size:.88rem">Chưa có video.</p>'; |
| |
| // Tab count for conditional display |
| let tabs=[ |
| {id:'specs',label:'Thông Số Kỹ Thuật',icon:'fa-list-check',content:specsHtml}, |
| {id:'feats',label:'Tính Năng',icon:'fa-star',content:featsHtml}, |
| {id:'desc',label:'Mô Tả Chi Tiết',icon:'fa-file-lines',content:descHtml}, |
| ]; |
| if(p.video)tabs.push({id:'video',label:'Video',icon:'fa-play',content:videoHtml}); |
| |
| let tabHeaders=tabs.map((t,i)=>`<div class="dtab-h${i===0?' active':''}" onclick="switchTab('${t.id}',this)"><i class="fas ${t.icon}"></i> ${t.label}</div>`).join(''); |
| let tabContents=tabs.map((t,i)=>`<div class="dtab-content${i===0?' active':''}" id="tab-${t.id}">${t.content}</div>`).join(''); |
| |
| // Related products (same category, max 4) |
| let related=D.filter(r=>r.catSlug===p.catSlug&&r!==p).slice(0,4); |
| let relatedHtml=''; |
| if(related.length){ |
| relatedHtml=`<div style="margin-top:30px"><h3 style="font-size:1.1rem;font-weight:700;margin-bottom:16px"><i class="fas fa-cubes" style="color:var(--a);margin-right:8px"></i>Sản phẩm liên quan</h3><div class="pg" style="grid-template-columns:repeat(auto-fill,minmax(200px,1fr))">`; |
| relatedHtml+=related.map(r=>{let ri=D.indexOf(r);return`<a class="pc" href="/san-pham/${r.slug}/index.html" onclick="event.preventDefault();showDetail(${ri});window.scrollTo({top:0,behavior:'smooth'})" style="display:block"><div class="pi" style="height:160px"><img src="${r.image}" alt="${r.name}" loading="lazy"></div><div class="pb"><div class="pn">${r.name}</div><div class="pp">${r.price||'Liên hệ'}</div></div></a>`}).join(''); |
| relatedHtml+='</div></div>'; |
| } |
| |
| dv.innerHTML=` |
| <div style="padding:28px 0"> |
| <div class="detail-back" onclick="goBack()"><i class="fas fa-arrow-left"></i> Quay lại danh sách</div> |
| <div class="detail-grid"> |
| <div class="gallery"> |
| <div class="gallery-carousel" id="galleryCarousel"> |
| <div class="gallery-track" id="galleryTrack">${slides}</div> |
| ${allImgs.length>1?`<button class="gallery-nav gn-prev" onclick="goSlide(_gSlide-1)"><i class="fas fa-chevron-left"></i></button><button class="gallery-nav gn-next" onclick="goSlide(_gSlide+1)"><i class="fas fa-chevron-right"></i></button>`:''} |
| </div> |
| ${dots} |
| ${thumbs?`<div class="gallery-thumbs">${thumbs}</div>`:''} |
| </div> |
| <div class="detail-info"> |
| ${p.sku?`<div class="detail-sku">SKU: ${p.sku} • Model: ${p.model}</div>`:''} |
| <div class="detail-cat"><i class="fas ${p.catIcon}"></i> ${p.cat}</div> |
| <h1 class="detail-name">${p.name}</h1> |
| <div style="font-size:.95rem;font-weight:800;color:${p.brand==='Eurogold'?'#c8102e':p.brand==='Grob'?'#2e7d32':'var(--a)'};letter-spacing:1px;margin-bottom:12px">${p.model?p.model+' | ':''} ${p.brand}</div> |
| <div class="detail-price">${p.price||'Liên hệ'}</div> |
| ${p.summary?`<div class="detail-summary">${p.summary}</div>`:''} |
| <div class="detail-badges"> |
| <div class="detail-badge"><i class="fas fa-check-circle"></i> Chính hãng ${p.brand}</div> |
| <div class="detail-badge"><i class="fas fa-shield-halved"></i> Bảo hành điện tử</div> |
| <div class="detail-badge"><i class="fas fa-truck-fast"></i> Miễn phí giao hàng TPHCM</div> |
| </div> |
| <div style="display:flex;gap:10px;flex-wrap:wrap"> |
| <button class="add-cart-btn" onclick="event.stopPropagation();addToCart(${idx})"><i class="fas fa-cart-plus"></i> Thêm vào giỏ</button> |
| <button class="detail-btn detail-btn-outline" onclick="event.stopPropagation();toggleChat()"><i class="fas fa-robot"></i> Tư Vấn AI</button> |
| <button class="detail-btn detail-btn-outline" onclick="event.stopPropagation();shareProduct('${p.slug}','${p.name.replace(/'/g,"\\'")}')" title="Chia sẻ sản phẩm"><i class="fas fa-share-alt"></i> Chia sẻ</button> |
| </div> |
| </div> |
| </div> |
| <div class="dtabs"> |
| <div class="dtab-headers">${tabHeaders}</div> |
| <div class="dtab-body">${tabContents}</div> |
| </div> |
| ${relatedHtml} |
| </div>`; |
| window.scrollTo({top:0,behavior:'smooth'}); |
| } |
| |
| function goBack(){ |
| document.getElementById('detailView').classList.remove('show'); |
| document.getElementById('detailView').innerHTML=''; |
| document.getElementById('listView').style.display=''; |
| document.querySelector('.hero').style.display=''; |
| document.title='V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi'; |
| // Reset OG meta |
| var defImg='https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_600.png'; |
| var el; |
| if(el=document.getElementById('ogTitle'))el.content='V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi'; |
| if(el=document.getElementById('ogDesc'))el.content='8000+ sản phẩm thiết bị nhà bếp & điện máy chính hãng'; |
| if(el=document.getElementById('ogImage'))el.content=defImg; |
| if(el=document.getElementById('ogUrl'))el.content=location.origin+'/'; |
| if(el=document.getElementById('twTitle'))el.content='V.AI STUDIO'; |
| if(el=document.getElementById('twDesc'))el.content='8000+ sản phẩm thiết bị nhà bếp & điện máy chính hãng'; |
| if(el=document.getElementById('twImage'))el.content=defImg; |
| history.pushState({type:'list'},'','/'); |
| } |
| |
| function goHome(){ |
| history.pushState({type:'list'},'','/'); |
| goBack();resetAll(); |
| } |
| |
| let _gSlide=0,_gTotal=0,_touchStartX=0; |
| function goSlide(i){ |
| let track=document.getElementById('galleryTrack'); |
| if(!track)return; |
| _gTotal=track.children.length; |
| if(i<0)i=0;if(i>=_gTotal)i=_gTotal-1; |
| _gSlide=i; |
| track.style.transform='translateX(-'+(_gSlide*100)+'%)'; |
| document.querySelectorAll('.gallery-dot').forEach((d,j)=>d.classList.toggle('active',j===_gSlide)); |
| document.querySelectorAll('.gallery-thumb').forEach((t,j)=>{t.classList.toggle('active',j===_gSlide);if(j===_gSlide)t.scrollIntoView({behavior:'smooth',block:'nearest',inline:'center'})}); |
| } |
| // Touch swipe support |
| document.addEventListener('touchstart',function(e){ |
| let c=e.target.closest('.gallery-carousel');if(c)_touchStartX=e.touches[0].clientX; |
| },{passive:true}); |
| document.addEventListener('touchend',function(e){ |
| if(!_touchStartX)return; |
| let c=e.target.closest('.gallery-carousel');if(!c){_touchStartX=0;return} |
| let diff=_touchStartX-e.changedTouches[0].clientX; |
| if(Math.abs(diff)>40){diff>0?goSlide(_gSlide+1):goSlide(_gSlide-1)} |
| _touchStartX=0; |
| },{passive:true}); |
| |
| function switchTab(id,el){ |
| document.querySelectorAll('.dtab-h').forEach(h=>h.classList.remove('active')); |
| document.querySelectorAll('.dtab-content').forEach(c=>c.classList.remove('active')); |
| el.classList.add('active'); |
| document.getElementById('tab-'+id).classList.add('active'); |
| } |
| |
| // ===== ACTIONS ===== |
| function setCat(s){S.cat=s;S.pg=1;updActive('.cat-item','data-s',s);goBackToList();updateHash();render();closeMsb()} |
| function setPrice(min,max,i){S.pmin=min;S.pmax=max;S.pg=1;document.querySelectorAll('.pr-item[data-i]').forEach((e,j)=>e.classList.toggle('active',j===i));goBackToList();updateHash();render();closeMsb()} |
| function setBrand(b){S.brand=b;S.pg=1;document.querySelectorAll('[data-b]').forEach(e=>e.classList.toggle('active',e.getAttribute('data-b')===b));goBackToList();updateHash();render();closeMsb()} |
| function goBackToList(){document.getElementById('listView').style.display='';document.getElementById('detailView').classList.remove('show');document.getElementById('detailView').innerHTML='';document.querySelector('.hero').style.display='';} |
| function doSearch(){S.q=document.getElementById('q').value.trim();S.pg=1;updateHash();render();renderMainCatSearch()} |
| function doSort(){S.sort=document.getElementById('ss').value;S.pg=1;updateHash();render()} |
| function setV(v){S.view=v;document.getElementById('gb').classList.toggle('active',v==='g');document.getElementById('lb').classList.toggle('active',v==='l');updateHash();render()} |
| function goPg(p){let f=getF(),tp=Math.ceil(f.length/PP);if(p<1||p>tp)return;S.pg=p;updateHash();render();document.querySelector('.toolbar').scrollIntoView({behavior:'smooth',block:'start'})} |
| function resetAll(){S={cat:'all',q:'',pmin:0,pmax:1e15,sort:'default',pg:1,view:S.view,brand:'all'};document.getElementById('q').value='';document.getElementById('ss').value='default';updActive('.cat-item','data-s','all');document.querySelectorAll('.pr-item[data-i]').forEach((e,i)=>e.classList.toggle('active',i===0));document.querySelectorAll('[data-b]').forEach(e=>e.classList.toggle('active',e.getAttribute('data-b')==='all'));history.replaceState({type:'list'},'','/');render();let csr=document.getElementById('catSearchResults');if(csr)csr.innerHTML='';} |
| function updActive(sel,attr,val){document.querySelectorAll(sel).forEach(e=>e.classList.toggle('active',e.getAttribute(attr)===val))} |
| function closeMsb(){document.getElementById('msb').classList.remove('open')} |
| document.addEventListener('keydown',e=>{if(e.key==='Escape')goBack();if(e.key==='/'&&document.activeElement.tagName!=='INPUT'){e.preventDefault();document.getElementById('q').focus()}}); |
| |
| // ===== CATALOGUE (all pages rendered as images, loaded from catalogues.json) ===== |
| let CAT_LIST=[]; |
| let catLoaded=false; |
| let activeCat=0; |
| const CAT_PER_PAGE=12; |
| let catPage=1; |
| |
| function loadCatalogueData(){ |
| return Promise.all([ |
| fetch('catalogues.json').then(r=>r.json()), |
| fetch('catalogue_ocr.json').then(r=>r.json()).catch(()=>[]) |
| ]).then(([data,ocr])=>{ |
| CAT_LIST=data; |
| window._catOcr=ocr; |
| initCatTabs(); |
| loadCatalogue(0); |
| catLoaded=true; |
| }); |
| } |
| |
| function initCatTabs(){ |
| let tabs=document.getElementById('catTabs'); |
| if(!tabs||!CAT_LIST.length)return; |
| const icons={'bep':'fa-fire','may-hut':'fa-wind','lo':'fa-temperature-high','chau':'fa-sink','khac':'fa-box','gia':'fa-tags','master':'fa-layer-group','grob':'fa-cogs','canzy':'fa-kitchen-set','demax':'fa-lock'}; |
| tabs.innerHTML=CAT_LIST.map((c,i)=>{ |
| let slug=c.slug||''; |
| let icon='fa-book-open'; |
| for(let k in icons){if(slug.includes(k)){icon=icons[k];break;}} |
| let pCount=c.pages?c.pages.length:0; |
| return `<div class="ctab${i===0?' active':''}" onclick="loadCatalogue(${i})"><i class="fas ${icon}"></i>${c.name||'Catalogue '+(i+1)}<span class="cnt">${pCount}</span></div>`; |
| }).join(''); |
| } |
| |
| function loadCatalogue(idx){ |
| activeCat=idx; |
| let c=CAT_LIST[idx]; |
| if(!c)return; |
| // Update active tab |
| let tabBtns=document.querySelectorAll('#catTabs .ctab'); |
| tabBtns.forEach((b,i)=>b.classList.toggle('active',i===idx)); |
| let content=document.getElementById('catContent'); |
| if(!content)return; |
| let pages=c.pages||[]; |
| let totalPages=pages.length; |
| let pageHtml=`<div style="text-align:center;margin-bottom:16px"> |
| <div style="font-size:.85rem;color:var(--g)">${c.desc||c.name||''} — <strong>${totalPages}</strong> trang</div> |
| </div>`; |
| pageHtml+=`<div style="display:flex;flex-direction:column;gap:16px;align-items:center">`; |
| pageHtml+=pages.map((pg,i)=>{ |
| // pg can be a URL string or an object with .file |
| let imgSrc=typeof pg==='string'?pg:(pg.file?'grob_img/'+pg.file:pg.image||pg.url||''); |
| let label='Trang '+(i+1); |
| return `<div style="width:100%;max-width:900px"><img src="${imgSrc}" alt="${label}" style="width:100%;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.1)" loading="lazy"><div style="text-align:center;font-size:.75rem;color:var(--g);margin-top:6px">${label}</div></div>`; |
| }).join(''); |
| pageHtml+=`</div>`; |
| content.innerHTML=pageHtml; |
| window.scrollTo({top:0,behavior:'smooth'}); |
| } |
| |
| function searchCatalogue(){ |
| let q=document.getElementById('catSearch').value.trim(); |
| if(!q){loadCatalogue(activeCat);document.getElementById('catSearchCount').textContent='';return} |
| let nq=nVN(q); |
| let results=[]; |
| CAT_LIST.forEach((c,ci)=>{ |
| let ocrTexts=(window._catOcr&&window._catOcr[ci])?window._catOcr[ci].texts||[]:[]; |
| (c.pages||[]).forEach((pg,pi)=>{ |
| let ocrText=ocrTexts[pi]||''; |
| let text=nVN(ocrText+' '+(c.name||'')); |
| if(smartSearch(nq,text)){ |
| let imgSrc=typeof pg==='string'?pg:(pg.file?'grob_img/'+pg.file:''); |
| results.push({cat:c.name||'Catalogue',imgSrc:imgSrc,pageNum:pi+1}); |
| } |
| }); |
| }); |
| document.getElementById('catSearchCount').textContent=results.length+' kết quả'; |
| let content=document.getElementById('catContent'); |
| if(!results.length){content.innerHTML='<div class="empty" style="padding:40px"><i class="fas fa-search"></i><h3>Không tìm thấy trong catalogue</h3></div>';return} |
| content.innerHTML=`<div style="display:flex;flex-direction:column;gap:16px;align-items:center">`+results.map(r=>`<div style="width:100%;max-width:900px"><div style="font-size:.8rem;color:var(--p);font-weight:700;margin-bottom:6px">${r.cat} — Trang ${r.pageNum}</div><img src="${r.imgSrc}" alt="Trang ${r.pageNum}" style="width:100%;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.1)" loading="lazy"></div>`).join('')+`</div>`; |
| } |
| |
| function renderMainCatSearch(){ |
| let q=document.getElementById('q').value.trim(); |
| let csr=document.getElementById('catSearchResults'); |
| if(!csr)return; |
| if(!q){csr.innerHTML='';return} |
| let nq=nVN(q); |
| let results=[]; |
| CAT_LIST.forEach((c,ci)=>{ |
| let ocrTexts=(window._catOcr&&window._catOcr[ci])?window._catOcr[ci].texts||[]:[]; |
| (c.pages||[]).forEach((pg,pi)=>{ |
| let ocrText=ocrTexts[pi]||''; |
| let text=nVN(ocrText+' '+(c.name||'')); |
| if(smartSearch(nq,text)){ |
| let imgSrc=typeof pg==='string'?pg:(pg.file?'grob_img/'+pg.file:''); |
| results.push({cat:c.name||'Catalogue',imgSrc:imgSrc,pageNum:pi+1}); |
| } |
| }); |
| }); |
| if(!results.length){csr.innerHTML='';return} |
| let searchVal=document.getElementById('q').value.replace(/'/g,"\\'"); |
| csr.innerHTML=`<div style="margin-top:24px;padding-top:24px;border-top:2px solid var(--gl)"><h3 style="font-size:1rem;font-weight:700;margin-bottom:12px"><i class="fas fa-book-open" style="color:var(--a);margin-right:8px"></i>Kết quả trong Catalogue (${results.length})</h3><div style="display:flex;flex-wrap:wrap;gap:12px">`+results.slice(0,6).map(r=>`<div style="width:200px;cursor:pointer" onclick="showPage('catalogue');document.getElementById('catSearch').value='${searchVal}';searchCatalogue();"><img src="${r.imgSrc}" style="width:100%;border-radius:8px;border:2px solid var(--gl)" loading="lazy"><div style="font-size:.7rem;color:var(--g);margin-top:4px">${r.cat} — Trang ${r.pageNum}</div></div>`).join('')+`</div></div>`; |
| } |
| |
| function showPage(page){ |
| document.querySelectorAll('.page-section').forEach(el=>el.classList.remove('active')); |
| document.querySelectorAll('.nav-tab').forEach(el=>el.classList.toggle('active',el.dataset.page===page)); |
| let el=document.getElementById('page-'+page); |
| if(el){el.classList.add('active');} |
| if(page==='catalogue'&&!catLoaded){loadCatalogueData();} |
| if(page==='catalogue'){history.pushState({type:'page',page:'catalogue'},'','/catalogue/index.html');} |
| else if(page==='contact'){history.pushState({type:'page',page:'contact'},'','/lien-he/index.html');} |
| else if(page==='products'){history.pushState({type:'list'},'','/');} |
| window.scrollTo({top:0,behavior:'smooth'}); |
| } |
| |
| function initCatalogue(){ |
| // catalogue loads on demand |
| } |
| |
| |
| // ===== CART ===== |
| let cart=JSON.parse(localStorage.getItem('malloca_cart')||'[]'); |
| |
| function updateCartBadge(){ |
| let n=cart.reduce((s,i)=>s+i.qty,0); |
| let badge=document.getElementById('cartNum'); |
| if(badge){badge.textContent=n;badge.style.display=n>0?'flex':'none'} |
| } |
| |
| function addToCart(idx){ |
| let p=D[idx]; |
| let exist=cart.find(c=>c.idx===idx); |
| if(exist){exist.qty++}else{cart.push({idx,name:p.name,price:p.price,priceNum:p.priceNum,image:p.image,qty:1})} |
| localStorage.setItem('malloca_cart',JSON.stringify(cart)); |
| updateCartBadge(); |
| // Show brief toast notification instead of opening cart |
| let toast=document.createElement('div'); |
| toast.style.cssText='position:fixed;bottom:24px;right:24px;background:var(--p);color:#fff;padding:12px 20px;border-radius:12px;font-size:.85rem;font-weight:600;z-index:999;display:flex;align-items:center;gap:8px;box-shadow:0 8px 24px rgba(0,0,0,.2);animation:fadeUp .3s ease'; |
| toast.innerHTML='<i class="fas fa-check-circle"></i> Đã thêm vào giỏ hàng'; |
| document.body.appendChild(toast); |
| setTimeout(()=>{toast.style.opacity='0';toast.style.transition='opacity .3s';setTimeout(()=>toast.remove(),300)},2000); |
| } |
| |
| function removeFromCart(i){ |
| cart.splice(i,1); |
| localStorage.setItem('malloca_cart',JSON.stringify(cart)); |
| updateCartBadge();renderCart(); |
| } |
| |
| function changeQty(i,delta){ |
| cart[i].qty+=delta; |
| if(cart[i].qty<1)cart.splice(i,1); |
| localStorage.setItem('malloca_cart',JSON.stringify(cart)); |
| updateCartBadge();renderCart(); |
| } |
| |
| function openCart(){ |
| document.getElementById('cartOverlay').classList.add('open'); |
| document.getElementById('cartDrawer').classList.add('open'); |
| document.body.style.overflow='hidden'; |
| renderCart(); |
| } |
| |
| function closeCart(){ |
| document.getElementById('cartOverlay').classList.remove('open'); |
| document.getElementById('cartDrawer').classList.remove('open'); |
| document.body.style.overflow=''; |
| } |
| |
| function renderCart(){ |
| let el=document.getElementById('cartItems'); |
| let footer=document.getElementById('cartFooter'); |
| if(!cart.length){ |
| el.innerHTML=`<div class="cart-empty"><i class="fas fa-shopping-bag"></i><h3>Giỏ hàng trống</h3><p style="font-size:.82rem">Thêm sản phẩm vào giỏ để đặt hàng</p></div>`; |
| footer.style.display='none'; |
| return; |
| } |
| footer.style.display='block'; |
| el.innerHTML=cart.map((c,i)=>`<div class="cart-item"><img src="${c.image}" alt="${c.name}"><div class="cart-item-info"><div class="cart-item-name">${c.name}</div><div class="cart-item-price">${c.price}</div><div class="cart-item-qty"><button class="qty-btn" onclick="changeQty(${i},-1)">−</button><span class="qty-val">${c.qty}</span><button class="qty-btn" onclick="changeQty(${i},1)">+</button></div></div><button class="cart-item-del" onclick="removeFromCart(${i})"><i class="fas fa-trash"></i></button></div>`).join(''); |
| let total=cart.reduce((s,c)=>s+c.priceNum*c.qty,0); |
| document.getElementById('cartTotal').textContent=total.toLocaleString('vi-VN')+'đ'; |
| } |
| |
| // ===== CHECKOUT ===== |
| function openCheckout(){ |
| closeCart(); |
| let overlay=document.getElementById('checkoutOverlay'); |
| overlay.classList.add('open'); |
| document.body.style.overflow='hidden'; |
| let total=cart.reduce((s,c)=>s+c.priceNum*c.qty,0); |
| let totalStr=total.toLocaleString('vi-VN')+'đ'; |
| // Build order summary |
| let itemsHtml=cart.map(c=>`<div class="checkout-summary-item"><span>${c.name} x${c.qty}</span><span style="font-weight:600">${(c.priceNum*c.qty).toLocaleString('vi-VN')}đ</span></div>`).join(''); |
| // Build QR URL (VietQR standard) |
| let qrUrl=`https://img.vietqr.io/image/VIB-918258385-compact2.jpg?amount=${total}&addInfo=${encodeURIComponent('Thanh toan don hang Malloca')}&accountName=${encodeURIComponent('TRAN QUOC VUONG')}`; |
| document.getElementById('checkoutContent').innerHTML=` |
| <h2 class="checkout-title"><i class="fas fa-qrcode" style="color:var(--a)"></i> Thanh Toán Đơn Hàng</h2> |
| <p class="checkout-sub">Quét mã QR để thanh toán qua ứng dụng ngân hàng</p> |
| <div class="checkout-summary">${itemsHtml}<div class="checkout-summary-item total"><span>Tổng cộng</span><span>${totalStr}</span></div></div> |
| <div class="form-group"><label>Họ tên người nhận *</label><input class="form-input" id="coName" placeholder="Nhập họ tên"></div> |
| <div class="form-group"><label>Số điện thoại *</label><input class="form-input" id="coPhone" placeholder="Nhập SĐT"></div> |
| <div class="form-group"><label>Địa chỉ giao hàng *</label><input class="form-input" id="coAddr" placeholder="Nhập địa chỉ đầy đủ"></div> |
| <div class="form-group"><label>Ghi chú</label><textarea class="form-input" id="coNote" placeholder="Ghi chú đơn hàng (nếu có)" style="height:70px"></textarea></div> |
| <div class="qr-section"> |
| <h3 style="font-size:1rem;font-weight:700;margin-bottom:14px"><i class="fas fa-university" style="color:var(--p);margin-right:6px"></i> Chuyển khoản ngân hàng</h3> |
| <img src="${qrUrl}" alt="QR Thanh toán" onerror="this.parentElement.innerHTML+='<p style=color:red>Không tải được QR. Vui lòng chuyển khoản thủ công.</p>'"> |
| <div class="qr-bank-info"> |
| <strong>Ngân hàng:</strong> VIB - Ngân hàng TMCP Quốc Tế Việt Nam<br> |
| <strong>Số TK:</strong> 918258385 <button class="copy-btn" onclick="copyText('918258385')"><i class="fas fa-copy"></i> Copy</button><br> |
| <strong>Chủ TK:</strong> TRẦN QUỐC VƯƠNG<br> |
| <strong>Số tiền:</strong> <span style="color:var(--p);font-weight:800">${totalStr}</span><br> |
| <strong>Nội dung CK:</strong> Thanh toan don hang Malloca <button class="copy-btn" onclick="copyText('Thanh toan don hang Malloca')"><i class="fas fa-copy"></i> Copy</button> |
| </div> |
| </div> |
| <button class="checkout-done-btn" onclick="confirmOrder()"><i class="fas fa-check-circle"></i> Xác nhận đã thanh toán</button> |
| <p class="checkout-note">Đơn hàng sẽ được xử lý sau khi xác nhận thanh toán thành công.<br>Zalo hỗ trợ: <a href="https://zalo.me/0981873395" target="_blank" style="color:var(--p)"><strong>0981 873 395</strong></a></p>`; |
| } |
| |
| function closeCheckout(){ |
| document.getElementById('checkoutOverlay').classList.remove('open'); |
| document.body.style.overflow=''; |
| } |
| |
| function copyText(text){ |
| navigator.clipboard.writeText(text).then(()=>{ |
| let btns=document.querySelectorAll('.copy-btn'); |
| btns.forEach(b=>{if(b.previousSibling&&b.previousSibling.textContent&&b.previousSibling.textContent.includes(text)){b.innerHTML='<i class="fas fa-check"></i> Đã copy';setTimeout(()=>{b.innerHTML='<i class="fas fa-copy"></i> Copy'},2000)}}); |
| }); |
| } |
| |
| function confirmOrder(){ |
| let name=document.getElementById('coName').value.trim(); |
| let phone=document.getElementById('coPhone').value.trim(); |
| let addr=document.getElementById('coAddr').value.trim(); |
| if(!name||!phone||!addr){alert('Vui lòng điền đầy đủ Họ tên, SĐT và Địa chỉ.');return} |
| // Clear cart |
| cart=[];localStorage.setItem('malloca_cart','[]');updateCartBadge(); |
| // Show success |
| document.getElementById('checkoutContent').innerHTML=` |
| <div style="text-align:center;padding:40px 0"> |
| <div style="width:80px;height:80px;background:rgba(40,167,69,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 20px"><i class="fas fa-check" style="font-size:2rem;color:#28a745"></i></div> |
| <h2 style="font-size:1.4rem;font-weight:800;color:var(--d);margin-bottom:8px">Đặt hàng thành công!</h2> |
| <p style="color:var(--g);font-size:.9rem;margin-bottom:24px">Cảm ơn bạn đã đặt hàng. Chúng tôi sẽ liên hệ xác nhận trong thời gian sớm nhất.</p> |
| <div style="background:var(--l);border-radius:12px;padding:16px;text-align:left;font-size:.85rem;color:var(--d);line-height:1.8"> |
| <strong>Người nhận:</strong> ${name}<br> |
| <strong>SĐT:</strong> ${phone}<br> |
| <strong>Địa chỉ:</strong> ${addr} |
| </div> |
| <button class="checkout-done-btn" onclick="closeCheckout();showPage('products')" style="margin-top:20px"><i class="fas fa-home"></i> Về trang chủ</button> |
| </div>`; |
| } |
| |
| // Contact form |
| function submitContact(e){ |
| e.preventDefault(); |
| let form=e.target; |
| let name=form.querySelector('#ctName').value; |
| let email=form.querySelector('#ctEmail').value; |
| let msg=form.querySelector('#ctMsg').value; |
| if(!name||!email||!msg){alert('Vui lòng điền đầy đủ thông tin.');return} |
| form.innerHTML=`<div style="text-align:center;padding:30px"><i class="fas fa-check-circle" style="font-size:2.5rem;color:#28a745;margin-bottom:12px;display:block"></i><h3 style="font-size:1.1rem;font-weight:700;margin-bottom:8px">Gửi thành công!</h3><p style="color:var(--g);font-size:.88rem">Cảm ơn bạn đã liên hệ. Chúng tôi sẽ phản hồi sớm nhất có thể.</p></div>`; |
| } |
| |
| // Override goHome to also handle page nav |
| let _origGoHome=goHome; |
| goHome=function(){ |
| if(document.getElementById('detailView').classList.contains('show')){ |
| goBack(); |
| } |
| showPage('products');resetAll(); |
| } |
| |
| // ===== SEO URL ROUTING ===== |
| |
| // Parse current path on load (for SPA navigation within iframe) |
| function parseCurrentPath(){ |
| let path=location.pathname; |
| |
| // Handle ?p= redirect from static OG pages |
| let qp=new URLSearchParams(location.search).get('p'); |
| if(qp){ |
| let idx=D.findIndex(p=>p.slug===qp); |
| if(idx>=0){showDetailNoHistory(idx);return true;} |
| } |
| |
| // Product page: /san-pham/<slug>/ (slug may contain slashes like dmx-tu-lanh/samsung-xxx) |
| let prodMatch=path.match(/^\/san-pham\/(.+?)(?:\/index\.html)?$/); |
| if(prodMatch){ |
| let slug=prodMatch[1].replace(/\/+$/,''); |
| if(slug==='index.html'||!slug)slug=null; |
| let idx=slug?D.findIndex(p=>p.slug===slug):-1; |
| if(idx>=0){showDetailNoHistory(idx);return true;} |
| } |
| |
| // Category page: /danh-muc/<slug>/ |
| let catMatch=path.match(/^\/danh-muc\/([^\/]+)/); |
| if(catMatch){ |
| let catSlug=catMatch[1]; |
| S.cat=catSlug; |
| updActive('.cat-item','data-s',catSlug); |
| document.querySelectorAll('[data-b]').forEach(e=>e.classList.toggle('active',e.getAttribute('data-b')==='all')); |
| render(); |
| return true; |
| } |
| |
| // Catalogue page |
| if(path.startsWith('/catalogue')){showPage('catalogue');return true;} |
| |
| // Contact page |
| if(path.startsWith('/lien-he')){showPage('contact');return true;} |
| |
| return false; |
| } |
| |
| // Also parse hash params (backward compatibility) |
| function parseUrlParams(){ |
| let hash=location.hash.replace(/^#/,''); |
| if(!hash)return; |
| let params=new URLSearchParams(hash); |
| |
| // Legacy hash page navigation |
| let pageParam=params.get('page'); |
| if(pageParam&&pageParam!=='products'){ |
| showPage(pageParam); |
| return; |
| } |
| |
| // Legacy product hash (supports both index and slug) |
| let productParam=params.get('product'); |
| if(productParam!==null){ |
| let idx=parseInt(productParam); |
| if(!isNaN(idx)&&idx>=0&&idx<D.length){showDetailNoHistory(idx);return;} |
| // Try slug match |
| let slugIdx=D.findIndex(p=>p.slug===productParam); |
| if(slugIdx>=0){showDetailNoHistory(slugIdx);return;} |
| } |
| |
| let q=params.get('q')||params.get('search')||''; |
| if(q){document.getElementById('q').value=q;S.q=q;S.pg=1;} |
| let cat=params.get('cat'); |
| if(cat){S.cat=cat;updActive('.cat-item','data-s',cat);} |
| let brand=params.get('brand'); |
| if(brand){S.brand=brand;document.querySelectorAll('[data-b]').forEach(e=>e.classList.toggle('active',e.getAttribute('data-b')===brand));} |
| let pgNum=params.get('pg'); |
| if(pgNum){S.pg=parseInt(pgNum)||1;} |
| let sort=params.get('sort'); |
| if(sort){S.sort=sort;document.getElementById('ss').value=sort;} |
| let p=params.get('p'); |
| if(p==='list'){S.view='l';document.getElementById('gb').classList.remove('active');document.getElementById('lb').classList.add('active');} |
| } |
| |
| // Update hash based on current list state (for filters) |
| function updateHash(){ |
| let parts=[]; |
| if(S.q)parts.push('q='+encodeURIComponent(S.q)); |
| if(S.cat!=='all')parts.push('cat='+encodeURIComponent(S.cat)); |
| if(S.brand!=='all')parts.push('brand='+encodeURIComponent(S.brand)); |
| if(S.pg>1)parts.push('pg='+S.pg); |
| if(S.sort!=='default')parts.push('sort='+encodeURIComponent(S.sort)); |
| if(S.view==='l')parts.push('p=list'); |
| let hash=parts.length?'#'+parts.join('&'):''; |
| history.replaceState({type:'list'},'','/'+hash); |
| } |
| |
| // Handle browser back/forward buttons |
| window.addEventListener('popstate',function(e){ |
| let state=e.state; |
| if(state&&state.type==='product'&&typeof state.idx==='number'){ |
| if(state.idx>=0&&state.idx<D.length){ |
| let dv=document.getElementById('detailView'); |
| if(!dv.classList.contains('show')){showDetailNoHistory(state.idx);} |
| } |
| }else{ |
| // Go back to list view |
| document.getElementById('detailView').classList.remove('show'); |
| document.getElementById('detailView').innerHTML=''; |
| document.getElementById('listView').style.display=''; |
| document.querySelector('.hero').style.display=''; |
| document.title='V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi'; |
| // Check if we need to show a specific page |
| let path=location.pathname; |
| if(path.startsWith('/catalogue')){showPage('catalogue');} |
| else if(path.startsWith('/lien-he')){showPage('contact');} |
| else{ |
| document.querySelectorAll('.page-section').forEach(el=>el.classList.remove('active')); |
| let prodPage=document.getElementById('page-products'); |
| if(prodPage)prodPage.classList.add('active'); |
| document.querySelectorAll('.nav-tab').forEach(el=>el.classList.toggle('active',el.dataset.page==='products')); |
| parseUrlParams(); |
| render(); |
| } |
| } |
| }); |
| |
| // showDetail without pushing history (for popstate) |
| function showDetailNoHistory(idx){ |
| _skipHistory=true; |
| showDetail(idx); |
| } |
| |
| // ===== SHARE (Web Share API + Popover fallback) ===== |
| let _shareData={title:'',text:'',url:''}; |
| |
| async function shareProduct(slug,name){ |
| let url=location.origin+'/san-pham/'+slug+'/index.html'; |
| let title=name||document.title; |
| let text=title+' - V.AI STUDIO'; |
| // Try native share first (includes image via OG tags already set by showDetail) |
| if(navigator.share){ |
| try{await navigator.share({title,text,url});return}catch(e){if(e.name==='AbortError')return} |
| } |
| // Fallback: show share popover |
| openSharePopover(url,title,text); |
| } |
| |
| function shareCurrentPage(){ |
| let url=location.origin+location.pathname; |
| let title=document.title; |
| let text=title; |
| if(navigator.share){ |
| navigator.share({title,text,url}).catch(()=>{}); |
| return; |
| } |
| openSharePopover(url,title,text); |
| } |
| |
| function openSharePopover(url,title,text){ |
| _shareData={title:title||'',text:text||'',url:url||location.href}; |
| document.getElementById('shareLinkInput').value=_shareData.url; |
| document.getElementById('shareOverlay').classList.add('open'); |
| } |
| function closeSharePopover(){document.getElementById('shareOverlay').classList.remove('open')} |
| |
| function shareVia(platform){ |
| let u=encodeURIComponent(_shareData.url); |
| let t=encodeURIComponent(_shareData.title); |
| let txt=encodeURIComponent(_shareData.text+' '+_shareData.url); |
| let win=null; |
| switch(platform){ |
| case'facebook':win=window.open('https://www.facebook.com/sharer/sharer.php?u='+u,'_blank','width=600,height=400');break; |
| case'messenger':win=window.open('https://www.facebook.com/dialog/send?link='+u+'&redirect_uri='+u,'_blank');break; |
| case'zalo':win=window.open('https://zalo.me/share?url='+u,'_blank');break; |
| case'twitter':win=window.open('https://twitter.com/intent/tweet?text='+t+'&url='+u,'_blank','width=600,height=400');break; |
| case'telegram':win=window.open('https://t.me/share/url?url='+u+'&text='+t,'_blank');break; |
| case'whatsapp':win=window.open('https://wa.me/?text='+txt,'_blank');break; |
| case'email':window.location.href='mailto:?subject='+t+'&body='+txt;break; |
| case'copy': |
| if(navigator.clipboard){navigator.clipboard.writeText(_shareData.url).then(()=>showToast('Đã sao chép link!'))} |
| else{let ta=document.createElement('textarea');ta.value=_shareData.url;ta.style.cssText='position:fixed;top:-9999px';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);showToast('Đã sao chép link!')} |
| break; |
| } |
| closeSharePopover(); |
| } |
| |
| function showToast(msg){ |
| let old=document.querySelector('.share-toast');if(old)old.remove(); |
| let t=document.createElement('div');t.className='share-toast'; |
| t.style.cssText='position:fixed;bottom:24px;right:24px;background:#28a745;color:#fff;padding:12px 20px;border-radius:12px;font-size:.85rem;font-weight:600;z-index:999;box-shadow:0 4px 12px rgba(0,0,0,.2)'; |
| t.innerHTML='<i class="fas fa-check-circle" style="margin-right:6px"></i>'+msg; |
| document.body.appendChild(t); |
| setTimeout(()=>{t.style.opacity='0';t.style.transition='opacity .3s';setTimeout(()=>t.remove(),300)},2500); |
| } |
| |
| // ===== AI CHATBOX ===== |
| let chatHistory=[]; |
| let chatBusy=false; |
| |
| function toggleChat(){ |
| let box=document.getElementById('chatbox'); |
| let ov=document.getElementById('chatOverlay2'); |
| if(box.classList.contains('open')){closeChat()} |
| else{box.classList.add('open');ov.classList.add('open');document.getElementById('chatInput').focus()} |
| } |
| function closeChat(){ |
| document.getElementById('chatbox').classList.remove('open'); |
| document.getElementById('chatOverlay2').classList.remove('open'); |
| } |
| |
| function sendSuggestion(el){ |
| document.getElementById('chatInput').value=el.textContent; |
| document.getElementById('chatSuggestions').style.display='none'; |
| sendChat(); |
| } |
| |
| function appendMsg(role,html){ |
| let body=document.getElementById('chatBody'); |
| let div=document.createElement('div'); |
| div.className='chat-msg '+role; |
| div.innerHTML=html; |
| // Add Zalo share button to bot messages |
| if(role==='bot'){ |
| let shareBtn=document.createElement('div'); |
| shareBtn.style.cssText='margin-top:8px;padding-top:6px;border-top:1px solid var(--gl);display:flex;gap:6px'; |
| shareBtn.innerHTML='<button onclick="shareChatToZalo(this.closest(\'.chat-msg\'))" style="padding:5px 12px;background:#0068FF;color:#fff;border:none;border-radius:6px;font-size:.72rem;font-weight:600;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:5px"><i class="fas fa-comment-dots"></i> Gửi qua Zalo</button>'; |
| div.appendChild(shareBtn); |
| } |
| body.appendChild(div); |
| body.scrollTop=body.scrollHeight; |
| return div; |
| } |
| |
| function shareChatToZalo(msgEl){ |
| // Lấy toàn bộ nội dung hội thoại |
| let body=document.getElementById('chatBody'); |
| let allMsgs=body.querySelectorAll('.chat-msg'); |
| let lines=[]; |
| allMsgs.forEach(m=>{ |
| let clone=m.cloneNode(true); |
| clone.querySelectorAll('div[style*="border-top"]').forEach(b=>b.remove()); |
| let txt=(clone.innerText||clone.textContent||'').trim(); |
| if(!txt)return; |
| let role=m.classList.contains('user')?'🧑 Khách:':'🤖 AI:'; |
| lines.push(role+' '+txt); |
| }); |
| let text=lines.join('\n\n').substring(0,3000); |
| text='[V.AI STUDIO - Tư vấn thiết bị nhà bếp]\n\n'+text+'\n\n---\nGửi từ: '+location.origin; |
| // Copy vào clipboard |
| if(navigator.clipboard){ |
| navigator.clipboard.writeText(text).catch(function(){ |
| let ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;top:-9999px'; |
| document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta); |
| }); |
| }else{ |
| let ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;top:-9999px'; |
| document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta); |
| } |
| // Mở Zalo profile — user nhấn "Nhắn tin" rồi paste |
| showToast('✅ Đã sao chép nội dung! Nhấn "Nhắn tin" trên Zalo rồi DÁN để gửi.'); |
| setTimeout(function(){window.open('https://zalo.me/0981873395','_blank')},800); |
| } |
| |
| function showTyping(){ |
| let body=document.getElementById('chatBody'); |
| let div=document.createElement('div'); |
| div.className='chat-typing';div.id='typingIndicator'; |
| div.innerHTML='<span></span><span></span><span></span>'; |
| body.appendChild(div); |
| body.scrollTop=body.scrollHeight; |
| } |
| function hideTyping(){let el=document.getElementById('typingIndicator');if(el)el.remove()} |
| |
| // Search products locally |
| function searchProducts(query){ |
| if(!D||!D.length)return[]; |
| // Use advanced search-plus-boot.js context search first. |
| // This handles Grob/Eurogold/Garis cabinet accessory dimensions such as: "kệ gia vị 300mm". |
| try{ |
| if(window._vaiSearchContext){ |
| let advanced=window._vaiSearchContext(query,8).map(x=>x.p).filter(Boolean); |
| if(advanced.length)return advanced; |
| } |
| }catch(e){console.warn('advanced searchProducts failed',e)} |
| let q=nVN(query); |
| let words=q.split(/\s+/).filter(w=>w.length>1); |
| let scored=D.map((p,i)=>{ |
| let idx=p._idx; |
| let score=0; |
| words.forEach(w=>{ |
| if(idx&&idx.includes(w))score+=1; |
| if(p.name&&nVN(p.name).includes(w))score+=3; |
| if(p.cat&&nVN(p.cat).includes(w))score+=2; |
| }); |
| return{p,score}; |
| }).filter(x=>x.score>0).sort((a,b)=>b.score-a.score); |
| return scored.slice(0,8).map(x=>x.p); |
| } |
| |
| // Format products for context |
| function productsToContext(products){ |
| return products.map(p=>{ |
| let specs=''; |
| if(p.specs&&Object.keys(p.specs).length){specs=' | Thông số: '+Object.entries(p.specs).slice(0,5).map(([k,v])=>k+': '+v).join(', ')} |
| return '- '+p.name+' | Giá: '+(p.price||'Liên hệ')+' | Danh mục: '+p.cat+specs; |
| }).join('\n'); |
| } |
| |
| // Format bot response |
| function formatBotResponse(text,matchedProducts){ |
| // Strip any raw URLs or markdown links — keep text only |
| text=text.replace(/\[([^\]]+)\]\([^)]+\)/g,'$1'); |
| text=text.replace(/https?:\/\/[^\s<)]+/g,''); |
| text=text.replace(/\/san-pham\/[a-z0-9-]+\/index\.html/g,''); |
| // Bold |
| text=text.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>'); |
| // Line breaks |
| text=text.replace(/\n/g,'<br>'); |
| // Smart product cards: only show products MENTIONED in the AI response |
| if(matchedProducts&&matchedProducts.length>0){ |
| let mentionedProducts=filterMentionedProducts(text,matchedProducts); |
| if(mentionedProducts.length>0){ |
| let cards=mentionedProducts.slice(0,4).map(p=>{ |
| let pidx=D.findIndex(d=>d.slug===p.slug); |
| let cartBtn=pidx>=0&&p.priceNum>0?'<button onclick="event.stopPropagation();addToCart('+pidx+');this.innerHTML=\'✅ Đã thêm\';this.disabled=true" style="padding:4px 10px;background:var(--a);color:#fff;border:none;border-radius:6px;font-size:.68rem;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:4px;font-family:inherit"><i class="fas fa-cart-plus"></i> Thêm giỏ</button>':''; |
| return '<div style="display:flex;gap:10px;background:var(--l);border-radius:10px;padding:10px;margin-top:6px;border:1px solid var(--gl)">' |
| +'<a href="/san-pham/'+p.slug+'/index.html" style="flex-shrink:0;text-decoration:none"><img src="'+p.image+'" style="width:50px;height:50px;object-fit:contain;border-radius:6px;background:#fff" onerror="this.style.display=\'none\'"></a>' |
| +'<div style="flex:1;min-width:0"><a href="/san-pham/'+p.slug+'/index.html" style="text-decoration:none;color:inherit"><div style="font-size:.76rem;font-weight:600;color:var(--d);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">'+p.name+'</div>' |
| +'<div style="font-size:.8rem;font-weight:800;color:var(--p);margin-top:2px">'+(p.price||'Liên hệ')+'</div></a>' |
| +'<div style="display:flex;gap:6px;margin-top:6px;flex-wrap:wrap">' |
| +cartBtn |
| +'<a href="/san-pham/'+p.slug+'/index.html" style="padding:4px 10px;background:var(--p);color:#fff;border-radius:6px;font-size:.68rem;font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:4px"><i class="fas fa-eye"></i> Xem</a>' |
| +'</div></div></div>'; |
| }).join(''); |
| text+='<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--gl)"><div style="font-size:.68rem;font-weight:700;color:var(--g);margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px">👉 Sản phẩm được đề cập</div>'+cards+'</div>'; |
| } |
| } |
| return text; |
| } |
| |
| // Filter products that are actually mentioned in the AI response |
| function filterMentionedProducts(responseText,products){ |
| if(!products||!products.length)return[]; |
| let text=responseText.toLowerCase().replace(/<[^>]*>/g,' '); |
| let textClean=text.replace(/[^a-z0-9]/g,''); |
| let mentioned=[]; |
| let scores=[]; |
| for(let p of products){ |
| let score=0; |
| // Check model code (strongest signal) |
| let model=(p.model||'').toLowerCase().replace(/[^a-z0-9]/g,''); |
| if(model.length>=4&&textClean.includes(model)){score+=10} |
| // Check significant name words (need >=3 matches for generic names) |
| let nameWords=p.name.toLowerCase().replace(/[|]/g,' ').split(/\s+/).filter(w=>w.length>3&&!['malloca','eurogold','grob','phụ','kiện'].includes(w)); |
| let matchCount=nameWords.filter(w=>text.includes(w)).length; |
| if(matchCount>=3){score+=matchCount} |
| if(score>0)scores.push({p,score}); |
| } |
| // Sort by score descending, take top matches |
| scores.sort((a,b)=>b.score-a.score); |
| return scores.slice(0,4).map(s=>s.p); |
| } |
| |
| async function sendChat(){ |
| if(chatBusy)return; |
| let input=document.getElementById('chatInput'); |
| let msg=input.value.trim(); |
| if(!msg)return; |
| input.value=''; |
| input.style.height='auto'; |
| document.getElementById('chatSuggestions').style.display='none'; |
| |
| // Show user message |
| appendMsg('user',msg.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>')); |
| |
| // Handle cart/quote/checkout commands |
| let msgLower=nVN(msg); |
| if(msgLower.match(/bao gia|lap bao gia|len bao gia|xuat bao gia|in bao gia/)){ |
| // Extract SKU codes and add to cart |
| let skuPart=msg.replace(/^.*?(b[aá]o\s*gi[aá]|l[âaà]p|xu[aấ]t)/i,'').trim(); |
| let codes=skuPart.split(/[,;\s]+/).map(s=>s.trim()).filter(s=>s.length>=3); |
| let addedNames=[]; |
| if(codes.length>0&&typeof D!=='undefined'&&D.length){ |
| codes.forEach(code=>{ |
| let cNorm=code.toLowerCase().replace(/[\-\s_.\/]/g,''); |
| for(let i=0;i<D.length;i++){ |
| let p=D[i];if(!p)continue; |
| let pSku=((p.sku||'')+(p.model||p.mod||'')).toLowerCase().replace(/[\-\s_.\/]/g,''); |
| let pName=(p.name||'').toLowerCase().replace(/[\-\s_.\/]/g,''); |
| if((pSku&&(pSku.indexOf(cNorm)!==-1||cNorm.indexOf(pSku)!==-1))||(cNorm.length>=5&&pName.indexOf(cNorm)!==-1)){ |
| if(typeof addToCart==='function')addToCart(i); |
| addedNames.push((p.sku||p.model||p.mod||code)+' - '+(p.name||'').substring(0,30)); |
| break; |
| } |
| } |
| }); |
| } |
| // Now render LIVE quote from current cart |
| if(cart.length===0&&addedNames.length===0){ |
| appendMsg('bot','Anh/chị cho em mã SP cần báo giá nhé! 😊<br>VD: <b>báo giá MH802</b> hoặc <b>báo giá MH802, MC9086HS</b>'); |
| chatBusy=false;return; |
| } |
| // Build quote table from CURRENT CART (real-time) |
| let total=cart.reduce((s,c)=>s+c.priceNum*c.qty,0); |
| let rows=cart.map((c,i)=>'<tr><td style="padding:5px;text-align:center;font-size:.72rem">'+(i+1)+'</td><td style="padding:5px">'+(c.image?'<img src="'+c.image+'" style="width:32px;height:32px;object-fit:contain;border-radius:4px" referrerpolicy="no-referrer" onerror="this.style.display=\'none\'">':'')+'</td><td style="padding:5px;font-size:.7rem;font-weight:600">'+c.name.substring(0,30)+'<br><span style="font-size:.58rem;color:#94a3b8">'+(c.sku||c.model||'')+'</span></td><td style="padding:5px;text-align:center;font-size:.7rem">'+c.qty+'</td><td style="padding:5px;text-align:right;font-size:.7rem;font-weight:700;white-space:nowrap">'+(c.priceNum?c.priceNum.toLocaleString('vi-VN')+'đ':'LH')+'</td></tr>').join(''); |
| let addedInfo=addedNames.length>0?'<div style="font-size:.7rem;color:#059669;margin-bottom:6px">✅ Đã thêm: '+addedNames.join(', ')+'</div>':''; |
| let qH='<div style="background:var(--l,#f8fafc);border-radius:10px;padding:10px;margin-top:6px;max-width:100%;overflow:hidden">'+addedInfo+'<div style="font-weight:800;font-size:.8rem;color:var(--p,#003f62);margin-bottom:6px">📋 BÁO GIÁ ('+cart.length+' SP)</div><div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;border:1px solid var(--gl,#e2e8f0);border-radius:6px;overflow:hidden"><thead><tr style="background:var(--p,#003f62);color:#fff"><th style="padding:4px;font-size:.6rem">#</th><th style="padding:4px;font-size:.6rem">Ảnh</th><th style="padding:4px;font-size:.6rem;text-align:left">SP</th><th style="padding:4px;font-size:.6rem">SL</th><th style="padding:4px;font-size:.6rem;text-align:right">Giá</th></tr></thead><tbody>'+rows+'</tbody><tfoot><tr style="background:rgba(0,63,98,.05)"><td colspan="4" style="padding:6px;font-weight:800;font-size:.72rem;text-align:right">TỔNG:</td><td style="padding:6px;font-weight:900;font-size:.8rem;color:var(--p,#003f62);text-align:right;white-space:nowrap">'+total.toLocaleString('vi-VN')+'đ</td></tr></tfoot></table></div><div style="display:flex;gap:4px;margin-top:8px;flex-wrap:wrap"><button onclick="if(typeof openQuotation===\'function\'){openQuotation();if(typeof closeChat===\'function\')closeChat();}" style="padding:5px 10px;background:var(--p,#003f62);color:#fff;border:none;border-radius:6px;font-size:.68rem;font-weight:700;cursor:pointer">📋 Lập phiếu báo giá</button><button onclick="if(window.VAI_ORDERS&&window.VAI_ORDERS.save)window.VAI_ORDERS.save()" style="padding:5px 10px;background:#7c3aed;color:#fff;border:none;border-radius:6px;font-size:.68rem;font-weight:700;cursor:pointer">💾 Lưu</button><button onclick="if(typeof shareQuoteImage===\'function\')shareQuoteImage()" style="padding:5px 10px;background:#25D366;color:#fff;border:none;border-radius:6px;font-size:.68rem;font-weight:700;cursor:pointer">📤 Chia sẻ</button></div><div style="font-size:.62rem;color:var(--g,#64748b);margin-top:6px;font-style:italic">💡 Nhắn thêm mã SP để bổ sung vào báo giá. VD: <b>báo giá K1603CL</b></div></div>'; |
| appendMsg('bot','Dạ em báo giá ngay ạ! 📋'+qH); |
| chatBusy=false;return; |
| } |
| |
| if(msgLower.match(/thanh toan|chuyen khoan|qr|quyet toan/)){ |
| if(cart.length===0){ |
| appendMsg('bot','Giỏ hàng đang trống ạ. Anh/chị chọn sản phẩm trước rồi em hỗ trợ thanh toán nhé! 😊'); |
| chatBusy=false;return; |
| } |
| openCheckout();closeChat(); |
| chatBusy=false;return; |
| } |
| |
| if(msgLower.match(/^gio hang$|^xem gio$|^xem gio hang$/)){ |
| if(cart.length===0){ |
| appendMsg('bot','Giỏ hàng đang trống ạ. Anh/chị muốn tìm sản phẩm gì để em tư vấn? 😊'); |
| }else{ |
| let items=cart.map((c,i)=>`${i+1}. **${c.name}** x${c.qty} — ${c.priceNum?c.priceNum.toLocaleString('vi-VN')+'đ':'Liên hệ'}`).join('<br>'); |
| let total=cart.reduce((s,c)=>s+c.priceNum*c.qty,0); |
| appendMsg('bot','🛒 Giỏ hàng hiện tại:<br>'+items+'<br><br><strong>Tổng: '+total.toLocaleString('vi-VN')+'đ</strong><br><br>Anh/chị có muốn em <strong>lên báo giá</strong> hoặc <strong>thanh toán</strong> không ạ?'); |
| } |
| chatBusy=false;return; |
| } |
| |
| // Search related products |
| let matchedProducts=searchProducts(msg); |
| let productContext=matchedProducts.length |
| ? matchedProducts.map((p,i)=>{ |
| let specs=''; |
| if(p.specs&&Object.keys(p.specs).length){ |
| specs=Object.entries(p.specs).slice(0,8).map(([k,v])=>k+': '+v).join('; '); |
| } |
| let features=(p.feats&&p.feats.length)?p.feats.slice(0,4).join('; '):''; |
| return `${i+1}. ${p.name} |
| Giá: ${p.price||'Liên hệ'} | ${p.cat} | ${p.brand} |
| ${specs?'Thông số: '+specs:''} |
| ${features?'Tính năng: '+features:''} |
| ${p.summary?'Mô tả: '+p.summary.substring(0,200):''}`; |
| }).join('\n\n') |
| : ''; |
| |
| // Prepare messages for AI |
| chatHistory.push({role:'user',content:msg}); |
| |
| let systemPrompt=`Bạn là V.AI STUDIO — chuyên gia tư vấn thiết bị nhà bếp & khóa thông minh, với nhiều năm kinh nghiệm. |
| |
| CÁCH TRẢ LỜI: |
| - Nói chuyện tự nhiên: xưng "em", gọi khách "anh/chị" |
| - PHÂN TÍCH nhu cầu cụ thể trước (diện tích bếp, số người, ngân sách) |
| - Giải thích TẠI SAO sản phẩm phù hợp — đừng chỉ liệt kê |
| - KHÔNG chèn link URL. KHÔNG dùng markdown link |
| - Dùng **in đậm** cho tên SP, emoji vừa phải |
| - Kết thúc bằng câu hỏi mở |
| |
| KHẢ NĂNG ĐẶC BIỆT — hướng dẫn khách dùng: |
| - Nếu khách muốn mua, hướng dẫn: "Anh/chị gõ **'thêm giỏ'** hoặc bấm nút 🛒 bên cạnh sản phẩm" |
| - Nếu khách muốn báo giá, hướng dẫn: "Anh/chị gõ **'báo giá'** để em lên bảng báo giá chi tiết" |
| - Nếu khách muốn thanh toán, hướng dẫn: "Anh/chị gõ **'thanh toán'** để chuyển khoản qua QR" |
| - Nếu khách muốn xem giỏ hàng: "Anh/chị gõ **'giỏ hàng'** để xem" |
| |
| ${cart.length>0?'GIỎHÀNG HIỆN TẠI ('+cart.length+' SP):\\n'+cart.map(c=>c.name+' x'+c.qty+' — '+(c.priceNum?c.priceNum.toLocaleString('vi-VN')+'đ':'Liên hệ')).join('\\n')+'\\nTổng: '+cart.reduce((s,c)=>s+c.priceNum*c.qty,0).toLocaleString('vi-VN')+'đ':'Giỏ hàng đang trống.'} |
| |
| ${productContext ? 'DỮ LIỆU SẢN PHẨM:\\n'+productContext : 'Không có SP khớp — tư vấn bằng kiến thức chuyên môn.'} |
| |
| THÔNG TIN: |
| - 2256 sản phẩm, 5 thương hiệu: Malloca, Eurogold, Grob, Canzy, Demax |
| - Malloca: thiết bị bếp cao cấp châu Âu |
| - Eurogold: phụ kiện tủ bếp inox 304 |
| - Grob: phụ kiện nhập khẩu, ưu tiên tư vấn Grob cho phụ kiện tủ bếp; với kệ/giá gia vị 300mm dùng kết quả tìm kiếm nâng cao từ web |
| - Canzy: bếp từ, máy hút mùi |
| - Demax: khóa cửa thông minh Made in Malaysia |
| - Zalo tư vấn: 0981 873 395 (zalo.me/0981873395) hoặc 0918 258 385 (zalo.me/0918258385)`; |
| |
| let messages=[{role:'system',content:systemPrompt}]; |
| // Keep last 8 messages for context |
| let recent=chatHistory.slice(-8); |
| messages.push(...recent); |
| |
| chatBusy=true; |
| document.getElementById('chatSend').disabled=true; |
| showTyping(); |
| |
| try{ |
| // Auto-detect token from any variable name |
| let vars=window.huggingface?.variables||{}; |
| let token=vars.HF_TOKEN||vars.VAISTUDIO||vars.AI_TOKEN||vars.TOKEN||''; |
| // Filter out non-token values |
| if(token&&!token.startsWith('hf_'))token=''; |
| |
| if(!token){ |
| hideTyping(); |
| // Smart local response without AI |
| let reply=''; |
| if(matchedProducts.length>0){ |
| reply='Tôi tìm thấy <strong>'+matchedProducts.length+' sản phẩm</strong> phù hợp với yêu cầu của bạn:'; |
| }else{ |
| reply='Tôi chưa tìm thấy sản phẩm cụ thể. Bạn có thể thử:<br>• Gõ tên sản phẩm (ví dụ: "bếp từ", "máy hút mùi")<br>• Gõ danh mục (ví dụ: "lò nướng", "chậu rửa")<br>• Gõ mã sản phẩm hoặc thương hiệu<br><br>💬 Hoặc nhắn Zalo <a href="https://zalo.me/0981873395" target="_blank" style="color:var(--p);font-weight:700">0981 873 395</a> để được tư vấn trực tiếp!'; |
| } |
| appendMsg('bot',formatBotResponse(reply,matchedProducts)); |
| chatHistory.push({role:'assistant',content:reply}); |
| chatBusy=false;document.getElementById('chatSend').disabled=false; |
| return; |
| } |
| |
| let resp=await fetch('https://router.huggingface.co/v1/chat/completions',{ |
| method:'POST', |
| headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'}, |
| body:JSON.stringify({ |
| model:'meta-llama/Llama-3.3-70B-Instruct', |
| messages:messages, |
| max_tokens:500, |
| temperature:0.7, |
| top_p:0.9, |
| stream:true |
| }) |
| }); |
| |
| hideTyping(); |
| |
| if(!resp.ok){ |
| let err=await resp.text(); |
| console.error('Chat API error:',resp.status,err); |
| appendMsg('bot',formatBotResponse('Xin lỗi, hệ thống AI đang bận. Đây là sản phẩm tôi tìm được cho anh/chị:',matchedProducts)); |
| chatBusy=false;document.getElementById('chatSend').disabled=false; |
| return; |
| } |
| |
| // Stream response — typewriter effect |
| let fullReply=''; |
| let botDiv=appendMsg('bot','<span class="typing-cursor">▊</span>'); |
| let reader=resp.body.getReader(); |
| let decoder=new TextDecoder(); |
| let buffer=''; |
| |
| while(true){ |
| let {done,value}=await reader.read(); |
| if(done)break; |
| buffer+=decoder.decode(value,{stream:true}); |
| let lines=buffer.split('\n'); |
| buffer=lines.pop(); |
| for(let line of lines){ |
| line=line.trim(); |
| if(!line.startsWith('data: '))continue; |
| let data=line.slice(6); |
| if(data==='[DONE]')break; |
| try{ |
| let j=JSON.parse(data); |
| let delta=j.choices?.[0]?.delta?.content; |
| if(delta){ |
| fullReply+=delta; |
| botDiv.innerHTML=formatBotResponse(fullReply,null)+'<span class="typing-cursor" style="display:inline;animation:blink .8s step-end infinite;color:var(--p)">▊</span>'; |
| document.getElementById('chatBody').scrollTop=document.getElementById('chatBody').scrollHeight; |
| } |
| }catch(e){} |
| } |
| } |
| // Remove cursor, add product cards |
| botDiv.innerHTML=formatBotResponse(fullReply,matchedProducts); |
| chatHistory.push({role:'assistant',content:fullReply}); |
| document.getElementById('chatBody').scrollTop=document.getElementById('chatBody').scrollHeight; |
| |
| }catch(e){ |
| hideTyping(); |
| console.error('Chat error:',e); |
| appendMsg('bot',formatBotResponse('Xin lỗi, đã có lỗi xảy ra. Đây là sản phẩm tôi tìm được cho anh/chị:',matchedProducts)); |
| } |
| |
| chatBusy=false; |
| document.getElementById('chatSend').disabled=false; |
| } |
| |
| // Legacy compatibility |
| function copyProductLink(slug){shareProduct(slug)} |
| function copyCurrentLink(){shareCurrentPage()} |
| function fallbackCopy(text){ |
| if(navigator.clipboard){navigator.clipboard.writeText(text).then(()=>showToast('Đã sao chép!'))} |
| else{let ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;top:-9999px';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);showToast('Đã sao chép!')} |
| } |
| |
| // ===== INLINE QUOTE VIEW + EXCEL EXPORT ===== |
| function viewQuoteInline(btn){ |
| let container=btn.closest('div').parentElement; |
| let existing=container.querySelector('.quote-inline-detail'); |
| if(existing){existing.remove();return;} |
| let today=new Date().toLocaleDateString('vi-VN'); |
| let total=cart.reduce((s,c)=>s+c.priceNum*c.qty,0); |
| let rows=cart.map((c,i)=>{ |
| let unit=c.priceNum?c.priceNum.toLocaleString('vi-VN')+'đ':'Liên hệ'; |
| let sub=(c.priceNum*c.qty).toLocaleString('vi-VN')+'đ'; |
| return `<tr style="border-bottom:1px solid var(--gl)"> |
| <td style="padding:10px 8px;font-size:.8rem;text-align:center;color:var(--g)">${i+1}</td> |
| <td style="padding:10px 8px"><div style="font-size:.8rem;font-weight:600">${c.name}</div></td> |
| <td style="padding:10px 8px;text-align:center;font-size:.8rem">${c.qty}</td> |
| <td style="padding:10px 8px;text-align:right;font-size:.8rem;white-space:nowrap">${unit}</td> |
| <td style="padding:10px 8px;text-align:right;font-size:.85rem;font-weight:700;color:var(--p);white-space:nowrap">${sub}</td> |
| </tr>`;}).join(''); |
| |
| let html=`<div class="quote-inline-detail" style="margin-top:12px;background:#fff;border-radius:12px;border:2px solid var(--p);overflow:hidden"> |
| <div style="background:linear-gradient(135deg,var(--p),#0077b6);color:#fff;padding:16px 18px"> |
| <div style="display:flex;align-items:center;gap:10px;margin-bottom:6px"> |
| <img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" style="height:30px;filter:brightness(10)" onerror="this.style.display='none'"> |
| <div style="font-weight:800;font-size:1rem">V.AI STUDIO</div> |
| </div> |
| <div style="font-size:.72rem;opacity:.8">Niềm tin khách hàng là tài sản của chúng tôi</div> |
| </div> |
| <div style="padding:16px 18px"> |
| <div style="display:flex;justify-content:space-between;margin-bottom:12px"> |
| <div><div style="font-size:.7rem;color:var(--g);text-transform:uppercase;letter-spacing:1px">Báo giá</div><div style="font-size:1.1rem;font-weight:800;color:var(--d)">BG-${Date.now().toString(36).toUpperCase()}</div></div> |
| <div style="text-align:right"><div style="font-size:.72rem;color:var(--g)">Ngày lập</div><div style="font-size:.85rem;font-weight:600">${today}</div></div> |
| </div> |
| <table style="width:100%;border-collapse:collapse"> |
| <thead><tr style="background:var(--l)"> |
| <th style="padding:8px;font-size:.7rem;text-align:center;color:var(--g);font-weight:700">STT</th> |
| <th style="padding:8px;font-size:.7rem;text-align:left;color:var(--g);font-weight:700">SẢN PHẨM</th> |
| <th style="padding:8px;font-size:.7rem;text-align:center;color:var(--g);font-weight:700">SL</th> |
| <th style="padding:8px;font-size:.7rem;text-align:right;color:var(--g);font-weight:700">ĐƠN GIÁ</th> |
| <th style="padding:8px;font-size:.7rem;text-align:right;color:var(--g);font-weight:700">THÀNH TIỀN</th> |
| </tr></thead> |
| <tbody>${rows}</tbody> |
| </table> |
| <div style="display:flex;justify-content:flex-end;padding:12px 8px;background:var(--l);border-radius:8px;margin-top:8px"> |
| <div style="text-align:right"><div style="font-size:.72rem;color:var(--g)">TỔNG CỘNG</div><div style="font-size:1.2rem;font-weight:900;color:var(--p)">${total.toLocaleString('vi-VN')}đ</div></div> |
| </div> |
| <div style="margin-top:12px;padding-top:12px;border-top:1px dashed var(--gl);font-size:.72rem;color:var(--g);line-height:1.6"> |
| 💬 Zalo: <a href="https://zalo.me/0981873395" target="_blank" style="color:var(--p);font-weight:600">0981 873 395</a> · <a href="https://zalo.me/0918258385" target="_blank" style="color:var(--p);font-weight:600">0918 258 385</a><br> |
| 💡 Giá đã bao gồm VAT · Miễn phí giao hàng trong TPHCM |
| </div> |
| </div> |
| </div>`; |
| container.insertAdjacentHTML('beforeend',html); |
| document.getElementById('chatBody').scrollTop=document.getElementById('chatBody').scrollHeight; |
| } |
| |
| function exportQuoteExcel(){ |
| if(!cart.length)return; |
| let today=new Date().toLocaleDateString('vi-VN'); |
| let total=cart.reduce((s,c)=>s+c.priceNum*c.qty,0); |
| // Build CSV with BOM for Excel UTF-8 |
| let csv='\uFEFF'; |
| csv+='BÁO GIÁ V.AI STUDIO\n'; |
| csv+='Ngày: '+today+'\n\n'; |
| csv+='STT,Mã SP,Sản phẩm,SL,Đơn giá,Thành tiền\n'; |
| cart.forEach((c,i)=>{ |
| let name=c.name.replace(/,/g,' -'); |
| let p=D[c.idx]; |
| let sku=p?p.sku||p.model:''; |
| csv+=(i+1)+','+sku+','+name+','+c.qty+','+(c.priceNum||0)+','+(c.priceNum*c.qty)+'\n'; |
| }); |
| csv+=',,,,,\n'; |
| csv+=',,,,TỔNG CỘNG,'+total+'\n'; |
| csv+='\n'; |
| csv+='Zalo: 0981 873 395 | 0918 258 385\n'; |
| csv+='Niềm tin khách hàng là tài sản của chúng tôi\n'; |
| let blob=new Blob([csv],{type:'text/csv;charset=utf-8'}); |
| let url=URL.createObjectURL(blob); |
| let a=document.createElement('a'); |
| a.href=url;a.download='BaoGia_VAI_STUDIO_'+today.replace(/\//g,'-')+'.csv'; |
| a.click();URL.revokeObjectURL(url); |
| showToast('Đã tải báo giá Excel!'); |
| } |
| |
| // ===== QUOTATION SYSTEM ===== |
| function openQuotation(){ |
| closeCart(); |
| let overlay=document.getElementById('quoteOverlay'); |
| overlay.classList.add('open'); |
| document.body.style.overflow='hidden'; |
| // Set default date |
| document.getElementById('qcDate').value=new Date().toISOString().split('T')[0]; |
| // Auto-generate order code |
| generateOrderCode(); |
| // Listen for name/date changes to regenerate |
| document.getElementById('qcName').addEventListener('input',generateOrderCode); |
| document.getElementById('qcDate').addEventListener('change',generateOrderCode); |
| // Build product table |
| renderQuoteTable(); |
| } |
| |
| function generateOrderCode(){ |
| let name=(document.getElementById('qcName').value||'').trim(); |
| let date=document.getElementById('qcDate').value||''; |
| // Extract initials from name (first letter of each word, uppercase) |
| let initials=name.split(/\s+/).map(w=>w.charAt(0)).join('').toUpperCase()||'KH'; |
| // Format date as DDMMYY |
| let dParts=date.split('-');// YYYY-MM-DD |
| let datePart=dParts.length===3?(dParts[2]+dParts[1]+dParts[0].slice(2)):(new Date().toLocaleDateString('en-GB').replace(/\//g,'').slice(0,6)); |
| let code=initials+datePart; |
| document.getElementById('qcCompany').value=code; |
| } |
| |
| function closeQuotation(){ |
| document.getElementById('quoteOverlay').classList.remove('open'); |
| document.body.style.overflow=''; |
| } |
| |
| let _quoteUnlocked=false; |
| |
| function checkQuoteAccess(){ |
| let code=document.getElementById('qcAccessCode').value; |
| let status=document.getElementById('qcAccessStatus'); |
| let icon=document.getElementById('qcLockIcon'); |
| let promptBox=document.getElementById('aiPromptBox'); |
| if(code==='V.AISTUDIO'){ |
| _quoteUnlocked=true;document.body.classList.add('vas-unlocked');localStorage.setItem('vas_quote_unlocked','1'); |
| status.textContent='🔓 Đã mở'; |
| status.style.color='#28a745'; |
| icon.className='fas fa-lock-open';icon.style.color='#28a745'; |
| promptBox.style.display='block';document.querySelectorAll('[data-order-btn]').forEach(function(b){b.style.display=''}); |
| document.querySelectorAll('.qt-disc').forEach(el=>{el.disabled=false;el.style.background='#fff';el.style.cursor='text'}); |
| }else{ |
| _quoteUnlocked=false; |
| status.textContent='🔒'; |
| status.style.color='var(--g)'; |
| icon.className='fas fa-lock';icon.style.color='var(--g)'; |
| promptBox.style.display='none'; |
| document.querySelectorAll('.qt-disc').forEach(el=>{el.disabled=true;el.style.background='var(--l)';el.style.cursor='not-allowed'}); |
| } |
| } |
| |
| async function applyAiDiscount(){ |
| let inp=document.getElementById('qcAiPrompt'); |
| let txt=inp?inp.value.trim():''; |
| let btn=document.getElementById('aiDiscBtn'); |
| let statusEl=document.getElementById('aiDiscStatus'); |
| if(!txt){if(statusEl)statusEl.textContent='VD: Malloca ck 35%, giao hàng 200k, lắp đặt 500k, cọc 5tr, ghi chú: giao thứ 7';return} |
| |
| let commands=txt.split(/[,;\n]+/).map(c=>c.trim()).filter(c=>c.length>1); |
| let results=[]; |
| let applied=0; |
| let fees=[]; |
| let deposit=0; |
| let depositPct=0; |
| let noteText=''; |
| |
| commands.forEach(cmd=>{ |
| let cmdLow=cmd.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d'); |
| |
| // GHI CHÚ: ghi chú: xxx hoặc note: xxx |
| if(cmdLow.match(/^(ghi chu|note|luu y)/)){ |
| noteText=cmd.replace(/^(ghi chú|ghi chu|note|lưu ý|luu y)[:\s]*/i,'').trim(); |
| results.push('📝 '+noteText); |
| return; |
| } |
| |
| // CK theo brand: malloca ck 35% |
| let brandCkMatch=cmdLow.match(/^(\w+)\s+(?:ck|chiet khau|giam|discount)\s*(\d+)\s*%?/); |
| if(brandCkMatch){ |
| let brandName=brandCkMatch[1];let pct=parseFloat(brandCkMatch[2]); |
| if(pct>0&&pct<=90){ |
| let count=0; |
| cart.forEach((c,i)=>{ |
| if(c.priceNum>0){ |
| let pBrand=(c.brand||'').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d'); |
| let pName=(c.name||'').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d'); |
| if(pBrand.indexOf(brandName)!==-1||pName.indexOf(brandName)!==-1){ |
| let dp=Math.round(c.priceNum*(1-pct/100)); |
| let el=document.querySelector('.qt-disc[data-idx="'+i+'"]'); |
| if(el){el.value=dp.toLocaleString('vi-VN');count++;} |
| } |
| } |
| }); |
| if(count>0){results.push(brandCkMatch[1]+' CK '+pct+'% ('+count+' SP)');applied+=count;} |
| return; |
| } |
| } |
| |
| // Giá theo mã SP: gov304190 giá 7tr |
| let skuPriceMatch=cmdLow.match(/^([a-z0-9_\-]+)\s+(?:gia|price)\s*([\d.,]+)\s*(tr|trieu|k|m|nghin)?/); |
| if(skuPriceMatch){ |
| let skuQuery=skuPriceMatch[1].replace(/[.\-_ ]/g,''); |
| let priceVal=parseFloat(skuPriceMatch[2].replace(/\./g,'').replace(',','.')); |
| let unit=(skuPriceMatch[3]||'').toLowerCase(); |
| if(unit==='tr'||unit==='trieu'||unit==='m')priceVal*=1000000; |
| else if(unit==='k'||unit==='nghin')priceVal*=1000; |
| else if(priceVal>0&&priceVal<500)priceVal*=1000000; |
| if(priceVal>0){ |
| cart.forEach((c,i)=>{ |
| let cSku=((c.sku||'')+(c.model||c.mod||'')).toLowerCase().replace(/[.\-_ ]/g,''); |
| if(cSku.indexOf(skuQuery)!==-1||skuQuery.indexOf(cSku)!==-1){ |
| let el=document.querySelector('.qt-disc[data-idx="'+i+'"]'); |
| if(el){el.value=priceVal.toLocaleString('vi-VN');applied++;results.push((c.sku||skuQuery)+' → '+priceVal.toLocaleString('vi-VN')+'đ');} |
| } |
| }); |
| } |
| return; |
| } |
| |
| // CK tất cả: ck 30% |
| let globalCkMatch=cmdLow.match(/^(?:ck|chiet khau|giam|discount)\s*(\d+)\s*%/); |
| if(globalCkMatch){ |
| let pct=parseFloat(globalCkMatch[1]); |
| if(pct>0&&pct<=90){ |
| let count=0; |
| cart.forEach((c,i)=>{ |
| if(c.priceNum>0){ |
| let dp=Math.round(c.priceNum*(1-pct/100)); |
| let el=document.querySelector('.qt-disc[data-idx="'+i+'"]'); |
| if(el){el.value=dp.toLocaleString('vi-VN');count++;} |
| } |
| }); |
| results.push('CK '+pct+'% tất cả ('+count+' SP)');applied+=count; |
| } |
| return; |
| } |
| |
| // CỌC: cọc 5tr, đặt cọc 30% |
| let cocMatch=cmdLow.match(/(?:coc|dat coc|deposit)\s*([\d.,]+)\s*(k|tr|trieu|%|m)?/); |
| if(cocMatch){ |
| let cocVal=parseFloat(cocMatch[1].replace(/\./g,'').replace(',','.')); |
| let cu=(cocMatch[2]||'').toLowerCase(); |
| if(cu==='%'){depositPct=cocVal;results.push('Cọc: '+cocVal+'%');} |
| else{ |
| if(cu==='k')cocVal*=1000;else if(cu==='tr'||cu==='trieu'||cu==='m')cocVal*=1000000; |
| else if(cocVal>0&&cocVal<500)cocVal*=1000000; |
| deposit=cocVal;results.push('Cọc: '+cocVal.toLocaleString('vi-VN')+'đ'); |
| } |
| return; |
| } |
| |
| // PHỤ PHÍ: giao hàng 200k, lắp đặt 500k, phí khác 100k, vận chuyển 300k |
| let feeMatch=cmdLow.match(/^(giao hang|giao|lap dat|lap|van chuyen|ship|phi giao|phi lap|phi khac|phu phi|phi)\s*([\d.,]+)\s*(k|tr|trieu|m|nghin)?/); |
| if(feeMatch){ |
| let feeAmt=parseFloat(feeMatch[2].replace(/\./g,'').replace(',','.')); |
| let u=(feeMatch[3]||'').toLowerCase(); |
| if(u==='k'||u==='nghin')feeAmt*=1000; |
| else if(u==='tr'||u==='trieu'||u==='m')feeAmt*=1000000; |
| else if(feeAmt>0&&feeAmt<500)feeAmt*=1000; |
| // Label |
| let labelMap={'giao hang':'Phí giao hàng','giao':'Phí giao hàng','lap dat':'Phí lắp đặt','lap':'Phí lắp đặt','van chuyen':'Phí vận chuyển','ship':'Phí vận chuyển','phi giao':'Phí giao hàng','phi lap':'Phí lắp đặt','phi khac':'Phí khác','phu phi':'Phụ phí','phi':'Phụ phí'}; |
| let feeLabel=labelMap[feeMatch[1]]||'Phụ phí'; |
| fees.push({label:feeLabel,amount:feeAmt}); |
| results.push(feeLabel+': +'+feeAmt.toLocaleString('vi-VN')+'đ'); |
| return; |
| } |
| |
| // Fallback: any line with number → treat as fee |
| let numMatch=cmdLow.match(/([\d.,]+)\s*(k|tr|trieu|m|nghin)?/); |
| if(numMatch){ |
| let amt=parseFloat(numMatch[1].replace(/\./g,'').replace(',','.')); |
| let u2=(numMatch[2]||'').toLowerCase(); |
| if(u2==='k'||u2==='nghin')amt*=1000; |
| else if(u2==='tr'||u2==='trieu'||u2==='m')amt*=1000000; |
| if(amt>0){ |
| let lb=cmdLow.replace(numMatch[0],'').replace(/[^a-z\s]/g,'').trim(); |
| if(!lb||lb.length<2)lb='Phụ phí'; |
| else lb=lb.charAt(0).toUpperCase()+lb.slice(1); |
| fees.push({label:lb,amount:amt}); |
| results.push(lb+': +'+amt.toLocaleString('vi-VN')+'đ'); |
| } |
| return; |
| } |
| }); |
| |
| updateQuoteTotal(); |
| |
| // Render fees + cọc + ghi chú vào bảng |
| document.querySelectorAll('.qt-extra-row').forEach(r=>r.remove()); |
| let tfoot=document.querySelector('.quote-table tfoot'); |
| if(tfoot){ |
| let totalCell=document.getElementById('quoteTotalCell'); |
| let totalVal=parseInt((totalCell?totalCell.textContent:'0').replace(/\D/g,''))||0; |
| let insertBefore=tfoot.querySelector('.quote-total-row'); |
| |
| // Thêm dòng phụ phí |
| fees.forEach(f=>{ |
| let row=document.createElement('tr');row.className='qt-extra-row'; |
| row.innerHTML='<td colspan="8" style="text-align:right;padding:6px 12px;font-size:.82rem;color:#92400e;font-weight:600">'+f.label+':</td><td class="qt-price" style="font-size:.82rem;color:#e53935;font-weight:700">+'+f.amount.toLocaleString('vi-VN')+'đ</td><td></td>'; |
| if(insertBefore)tfoot.insertBefore(row,insertBefore);else tfoot.appendChild(row); |
| totalVal+=f.amount; |
| }); |
| |
| // Cập nhật tổng |
| if(fees.length&&totalCell)totalCell.textContent=totalVal.toLocaleString('vi-VN')+'đ'; |
| |
| // Cọc |
| if(deposit>0||depositPct>0){ |
| let cocAmt=deposit>0?deposit:Math.round(totalVal*depositPct/100); |
| let cocRow=document.createElement('tr');cocRow.className='qt-extra-row'; |
| cocRow.innerHTML='<td colspan="8" style="text-align:right;padding:6px 12px;font-size:.82rem;color:#2563eb;font-weight:600">Đặt cọc'+(depositPct>0?' ('+depositPct+'%)':'')+':</td><td class="qt-price" style="font-size:.82rem;color:#2563eb;font-weight:700">'+cocAmt.toLocaleString('vi-VN')+'đ</td><td></td>'; |
| tfoot.appendChild(cocRow); |
| let remainRow=document.createElement('tr');remainRow.className='qt-extra-row'; |
| remainRow.innerHTML='<td colspan="8" style="text-align:right;padding:6px 12px;font-size:.82rem;color:#059669;font-weight:600">Còn lại:</td><td class="qt-price" style="font-size:.82rem;color:#059669;font-weight:700">'+(totalVal-cocAmt).toLocaleString('vi-VN')+'đ</td><td></td>'; |
| tfoot.appendChild(remainRow); |
| } |
| } |
| |
| // Điền ghi chú vào ô ghi chú (nếu có) |
| if(noteText){ |
| let noteInputs=document.querySelectorAll('.qt-note,textarea[placeholder*="ghi chú"],input[placeholder*="ghi chú"]'); |
| noteInputs.forEach(el=>{el.value=noteText;}); |
| // Also try quote table note cells |
| document.querySelectorAll('input.qt-note,textarea.qt-note').forEach(el=>{if(!el.value)el.value=noteText;}); |
| } |
| |
| if(statusEl){ |
| if(results.length)statusEl.innerHTML='✅ '+results.join(' | '); |
| else statusEl.textContent='⚠️ Không nhận diện. VD: Malloca ck 35%, giao hàng 200k, lắp 500k, cọc 5tr, ghi chú: giao thứ 7'; |
| } |
| } |
| |
| |
| function renderQuoteTable(){ |
| let tbody=document.getElementById('quoteTableBody'); |
| let locked=!_quoteUnlocked; |
| tbody.innerHTML=cart.map((c,i)=>{ |
| let product=D[c.idx]||{}; |
| let model=product.model||product.sku||''; |
| let specs=''; |
| if(product.specs&&Object.keys(product.specs).length){ |
| specs=Object.entries(product.specs).slice(0,3).map(([k,v])=>k+': '+v).join(', '); |
| } |
| let lineTotal=c.priceNum*c.qty; |
| let discStyle='width:80px;padding:5px 6px;border:1.5px solid var(--gl);border-radius:6px;font-size:.78rem;text-align:right;font-family:inherit;outline:none;'; |
| discStyle+=locked?'background:var(--l);cursor:not-allowed':'background:#fff'; |
| return `<tr> |
| <td style="text-align:center">${i+1}</td> |
| <td><img class="qt-img" src="${c.image}" referrerpolicy="no-referrer" alt="${c.name}" onerror="this.style.display='none'" style="width:72px;height:72px;object-fit:contain"></td> |
| <td class="qt-name">${c.name}</td> |
| <td style="text-align:center">${model}</td> |
| <td style="font-size:.72rem;color:var(--g);max-width:160px">${specs}</td> |
| <td style="text-align:center">${c.qty}</td> |
| <td class="qt-price">${c.priceNum>0?c.priceNum.toLocaleString('vi-VN')+'đ':'Liên hệ'}</td> |
| <td><input class="qt-disc" data-idx="${i}" value="${c.priceNum>0?c.priceNum.toLocaleString('vi-VN'):''}" oninput="updateQuoteTotal()" ${locked?'disabled':''} style="${discStyle}"></td> |
| <td class="qt-price qt-linetotal" data-idx="${i}">${lineTotal>0?lineTotal.toLocaleString('vi-VN')+'đ':''}</td> |
| <td><input class="qt-note" data-idx="${i}" placeholder="Ghi chú"></td> |
| </tr>`; |
| }).join(''); |
| updateQuoteTotal(); |
| } |
| |
| function updateQuoteTotal(){ |
| let total=0; |
| cart.forEach((c,i)=>{ |
| let discInput=document.querySelector(`.qt-disc[data-idx="${i}"]`); |
| let discVal=discInput?(parseInt(discInput.value.replace(/\D/g,''))||0)||0:c.priceNum; |
| let lineTotal=discVal*c.qty; |
| let cell=document.querySelector(`.qt-linetotal[data-idx="${i}"]`); |
| if(cell)cell.textContent=lineTotal>0?lineTotal.toLocaleString('vi-VN')+'đ':''; |
| total+=lineTotal; |
| }); |
| document.getElementById('quoteTotalCell').textContent=total.toLocaleString('vi-VN')+'đ'; |
| } |
| |
| function getQuoteData(){ |
| let customer={ |
| name:document.getElementById('qcName').value||'', |
| phone:document.getElementById('qcPhone').value||'', |
| email:document.getElementById('qcEmail').value||'', |
| addr:document.getElementById('qcAddr').value||'', |
| orderCode:document.getElementById('qcCompany').value||'', |
| date:document.getElementById('qcDate').value||new Date().toISOString().split('T')[0] |
| }; |
| let items=cart.map((c,i)=>{ |
| let product=D[c.idx]||{}; |
| let discInput=document.querySelector(`.qt-disc[data-idx="${i}"]`); |
| let noteInput=document.querySelector(`.qt-note[data-idx="${i}"]`); |
| let discPrice=discInput?parseInt(discInput.value.replace(/\D/g,''))||0:c.priceNum; |
| return{ |
| stt:i+1, name:c.name, model:product.model||product.sku||'', |
| specs:product.specs?Object.entries(product.specs).slice(0,3).map(([k,v])=>k+': '+v).join(', '):'', |
| image:c.image, qty:c.qty, price:c.priceNum, discPrice:discPrice, |
| total:discPrice*c.qty, note:noteInput?noteInput.value:'' |
| }; |
| }); |
| let grandTotal=items.reduce((s,it)=>s+it.total,0); |
| return{customer,items,grandTotal}; |
| } |
| |
| function fmtVND(n){return n>0?n.toLocaleString('vi-VN')+'đ':'Liên hệ'} |
| |
| // ===== EXCEL EXPORT WITH FORMULAS ===== |
| async function exportExcel(){ |
| let qd=getQuoteData(); |
| let wb=new ExcelJS.Workbook(); |
| wb.creator='Malloca Vietnam'; |
| let ws=wb.addWorksheet('Báo Giá'); |
| ws.columns=[ |
| {key:'stt',width:6},{key:'name',width:32},{key:'model',width:16},{key:'specs',width:28}, |
| {key:'image',width:18},{key:'qty',width:8},{key:'price',width:16},{key:'disc',width:16}, |
| {key:'total',width:18},{key:'note',width:18} |
| ]; |
| // Header |
| ws.mergeCells('A1','J1'); |
| let h1=ws.getCell('A1'); |
| h1.value='V.AI STUDIO - Niềm tin khách hàng là tài sản của chúng tôi'; |
| h1.font={name:'Arial',bold:true,size:14,color:{argb:'FF003F62'}}; |
| h1.alignment={horizontal:'center',vertical:'middle'}; |
| h1.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0F7FF'}}; |
| ws.getRow(1).height=30; |
| ws.mergeCells('A2','J2'); |
| let h2=ws.getCell('A2'); |
| h2.value='BẢNG BÁO GIÁ'; |
| h2.font={name:'Arial',bold:true,size:16,color:{argb:'FFDB9815'}}; |
| h2.alignment={horizontal:'center',vertical:'middle'}; |
| ws.getRow(2).height=28; |
| |
| // Customer info |
| let r=4; |
| let ci=[['Khách hàng:',qd.customer.name],['SĐT:',qd.customer.phone],['Email:',qd.customer.email], |
| ['Địa chỉ:',qd.customer.addr],['Mã đơn hàng:',qd.customer.orderCode],['Ngày:',qd.customer.date]]; |
| ci.forEach(pair=>{ |
| ws.getCell('A'+r).value=pair[0];ws.getCell('A'+r).font={bold:true,size:10}; |
| ws.getCell('B'+r).value=pair[1];ws.getCell('B'+r).font={size:10}; |
| r++; |
| }); |
| r++; |
| let headerR=r; |
| |
| // Table header |
| let headers=['STT','Tên sản phẩm','Mã SP','Thông tin SP','Hình ảnh','SL','Đơn giá','Đơn giá CK','Thành tiền','Ghi chú']; |
| let headerRow=ws.getRow(r); |
| headers.forEach((h,i)=>{headerRow.getCell(i+1).value=h}); |
| headerRow.height=26; |
| headerRow.eachCell(cell=>{ |
| cell.font={bold:true,color:{argb:'FFFFFFFF'},name:'Arial',size:9}; |
| cell.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}}; |
| cell.border={top:{style:'thin'},left:{style:'thin'},bottom:{style:'thin'},right:{style:'thin'}}; |
| cell.alignment={horizontal:'center',vertical:'middle',wrapText:true}; |
| }); |
| r++; |
| let dataStartR=r; |
| |
| // Data rows with FORMULAS |
| // Detect if AI applied a percentage discount |
| let discountPct=0; |
| if(qd.items.length>0&&qd.items[0].price>0){ |
| let ratio=qd.items[0].discPrice/qd.items[0].price; |
| let allSameRatio=qd.items.every(it=>it.price===0||Math.abs(it.discPrice/it.price-ratio)<0.005); |
| if(allSameRatio&&ratio<1&&ratio>0)discountPct=Math.round(ratio*100); |
| } |
| |
| qd.items.forEach((item,idx)=>{ |
| let row=ws.getRow(r); |
| row.height=64; |
| // A: STT |
| let cA=row.getCell(1);cA.value=item.stt; |
| cA.alignment={horizontal:'center',vertical:'middle'}; |
| cA.font={size:10}; |
| // B: Tên SP |
| let cB=row.getCell(2);cB.value=item.name; |
| cB.alignment={vertical:'middle',wrapText:true}; |
| cB.font={size:10,bold:true,color:{argb:'FF1A1A1A'}}; |
| // C: Mã SP |
| let cC=row.getCell(3);cC.value=item.model; |
| cC.alignment={horizontal:'center',vertical:'middle'}; |
| cC.font={size:9,color:{argb:'FF003F62'}}; |
| // D: Thông tin |
| let cD=row.getCell(4);cD.value=item.specs; |
| cD.alignment={vertical:'middle',wrapText:true}; |
| cD.font={size:8,color:{argb:'FF888888'}}; |
| // E: Hình ảnh (embedded from DOM) |
| row.getCell(5).value=''; |
| row.getCell(5).alignment={horizontal:'center',vertical:'middle'}; |
| // Try to embed image from the quote table's <img> element |
| try{ |
| let imgEl=document.querySelector('.qt-img[alt="'+item.name.substring(0,30)+'"]')||document.querySelectorAll('.qt-img')[idx]; |
| if(imgEl&&imgEl.complete&&imgEl.naturalWidth>0){ |
| let cvs=document.createElement('canvas'); |
| cvs.width=Math.min(imgEl.naturalWidth,120); |
| cvs.height=Math.min(imgEl.naturalHeight,120); |
| let ctx=cvs.getContext('2d'); |
| ctx.drawImage(imgEl,0,0,cvs.width,cvs.height); |
| try{ |
| let b64=cvs.toDataURL('image/jpeg',0.7).split(',')[1]; |
| let imgId=wb.addImage({base64:b64,extension:'jpeg'}); |
| ws.addImage(imgId,{tl:{col:4,row:r-1},ext:{width:70,height:70}}); |
| }catch(ce){} |
| } |
| }catch(ie){} |
| // F: SL |
| let cF=row.getCell(6);cF.value=item.qty; |
| cF.alignment={horizontal:'center',vertical:'middle'}; |
| cF.font={size:10,bold:true}; |
| // G: Đơn giá |
| let cG=row.getCell(7);cG.value=Number(item.price)||0;cG.numFmt='#,##0'; |
| cG.numFmt='#,##0'; |
| cG.alignment={horizontal:'right',vertical:'middle'}; |
| cG.font={size:10}; |
| // H: Đơn giá CK = FORMULA if %, else value |
| let cH=row.getCell(8); |
| if(discountPct>0&&discountPct<100){ |
| cH.value={formula:`G${r}*${discountPct}%`}; |
| }else{ |
| cH.value=Number(item.discPrice)||Number(item.price)||0; |
| } |
| cH.numFmt='#,##0'; |
| cH.alignment={horizontal:'right',vertical:'middle'}; |
| if(item.discPrice<item.price){cH.font={size:10,color:{argb:'FFDC3545'},bold:true}}else{cH.font={size:10}} |
| // I: Thành tiền = H × F |
| let cI=row.getCell(9); |
| cI.value={formula:`H${r}*F${r}`};cI.numFmt='#,##0'; |
| cI.numFmt='#,##0'; |
| cI.alignment={horizontal:'right',vertical:'middle'}; |
| cI.font={size:10,bold:true,color:{argb:'FF003F62'}}; |
| // J: Ghi chú |
| let cJ=row.getCell(10);cJ.value=item.note; |
| cJ.alignment={vertical:'middle',wrapText:true}; |
| cJ.font={size:9}; |
| // Borders + alternate colors |
| for(let c=1;c<=10;c++){ |
| row.getCell(c).border={top:{style:'thin',color:{argb:'FFD0D5DD'}},left:{style:'thin',color:{argb:'FFD0D5DD'}},bottom:{style:'thin',color:{argb:'FFD0D5DD'}},right:{style:'thin',color:{argb:'FFD0D5DD'}}}; |
| } |
| if(item.stt%2===0){for(let c=1;c<=10;c++){row.getCell(c).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF8FAFC'}}}} |
| r++; |
| }); |
| let dataEndR=r-1; |
| |
| // ═══ SUMMARY SECTION ═══ |
| let sumStyle={border:{top:{style:'thin',color:{argb:'FFD0D5DD'}},bottom:{style:'thin',color:{argb:'FFD0D5DD'}}}}; |
| |
| // Number format for prices |
| ws.getColumn(7).numFmt='#,##0';ws.getColumn(8).numFmt='#,##0';ws.getColumn(9).numFmt='#,##0'; |
| |
| // TỔNG CỘNG |
| let tr=r; |
| ws.mergeCells(tr,1,tr,8); |
| ws.getCell('A'+tr).value='TỔNG CỘNG:'; |
| ws.getCell('A'+tr).font={bold:true,size:11,color:{argb:'FF003F62'}}; |
| ws.getCell('A'+tr).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('I'+tr).value={formula:`SUM(I${dataStartR}:I${dataEndR})`}; |
| ws.getCell('I'+tr).numFmt='#,##0'; |
| ws.getCell('I'+tr).font={bold:true,size:12,color:{argb:'FF003F62'}}; |
| ws.getCell('I'+tr).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getRow(tr).height=28; |
| for(let c=1;c<=10;c++){ws.getRow(tr).getCell(c).border={top:{style:'medium',color:{argb:'FF003F62'}},bottom:{style:'thin',color:{argb:'FFD0D5DD'}}};ws.getRow(tr).getCell(c).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFE8F0FE'}}} |
| r=tr+1; |
| |
| // ĐẶT CỌC (30%) |
| ws.mergeCells(r,1,r,8); |
| ws.getCell('A'+r).value='Đặt cọc (30%):'; |
| ws.getCell('A'+r).font={size:10,color:{argb:'FF555555'}}; |
| ws.getCell('A'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('I'+r).value={formula:`ROUND(I${tr}*30%,0)`}; |
| ws.getCell('I'+r).numFmt='#,##0'; |
| ws.getCell('I'+r).font={bold:true,size:10,color:{argb:'FFDB9815'}}; |
| ws.getCell('I'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getRow(r).height=22; |
| r++; |
| |
| // CÒN LẠI |
| ws.mergeCells(r,1,r,8); |
| ws.getCell('A'+r).value='Còn lại:'; |
| ws.getCell('A'+r).font={size:10,color:{argb:'FF555555'}}; |
| ws.getCell('A'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('I'+r).value={formula:`I${tr}-I${r-1}`}; |
| ws.getCell('I'+r).numFmt='#,##0'; |
| ws.getCell('I'+r).font={size:10}; |
| ws.getCell('I'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getRow(r).height=22; |
| r++; |
| |
| // PHỤ PHÍ |
| ws.mergeCells(r,1,r,8); |
| ws.getCell('A'+r).value='Phụ phí (nếu có):'; |
| ws.getCell('A'+r).font={size:10,color:{argb:'FF555555'}}; |
| ws.getCell('A'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('I'+r).value=0; |
| ws.getCell('I'+r).numFmt='#,##0'; |
| ws.getCell('I'+r).font={size:10,italic:true}; |
| ws.getCell('I'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('I'+r).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFFFFFEE'}}; |
| let surchargeR=r; |
| ws.getRow(r).height=22; |
| r++; |
| |
| // TỔNG THANH TOÁN |
| ws.mergeCells(r,1,r,8); |
| ws.getCell('A'+r).value='TỔNG THANH TOÁN:'; |
| ws.getCell('A'+r).font={bold:true,size:12,color:{argb:'FFFFFFFF'}}; |
| ws.getCell('A'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('A'+r).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}}; |
| ws.getCell('I'+r).value={formula:`I${tr}+I${surchargeR}`}; |
| ws.getCell('I'+r).numFmt='#,##0'; |
| ws.getCell('I'+r).font={bold:true,size:13,color:{argb:'FFFFFFFF'}}; |
| ws.getCell('I'+r).alignment={horizontal:'right',vertical:'middle'}; |
| ws.getCell('I'+r).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}}; |
| for(let c=1;c<=10;c++){ws.getRow(r).getCell(c).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}}} |
| ws.getRow(r).height=30; |
| r+=2; |
| |
| // Footer notes |
| ws.mergeCells(r,1,r,10); |
| ws.getCell('A'+r).value='📌 Miễn phí giao hàng trong TPHCM. Giá đã bao gồm VAT (Hỗ trợ trị giá đơn hàng VAT xuất theo đúng số tiền khác mã).'; |
| ws.getCell('A'+r).font={italic:true,size:9,color:{argb:'FF888888'}}; |
| ws.getCell('A'+r).alignment={wrapText:true}; |
| r++; |
| ws.mergeCells(r,1,r,10); |
| ws.getCell('A'+r).value='💬 Zalo: 0981 873 395 | 0918 258 385'; |
| ws.getCell('A'+r).font={italic:true,size:9,color:{argb:'FF888888'}}; |
| ws.getCell('A'+r).alignment={wrapText:true}; |
| |
| // Save |
| let buf=await wb.xlsx.writeBuffer(); |
| let blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); |
| let url=URL.createObjectURL(blob); |
| let a=document.createElement('a');a.href=url;a.download=(qd.customer.orderCode||'BG')+'.xlsx'; |
| a.click();URL.revokeObjectURL(url); |
| showToast('Đã xuất file Excel!'); |
| } |
| |
| // ===== PDF EXPORT (html2canvas for Vietnamese) ===== |
| async function exportPDF(){ |
| let qd=getQuoteData(); |
| // Build a hidden HTML table for rendering |
| let div=document.createElement('div'); |
| div.id='pdfRender'; |
| div.style.cssText='position:fixed;top:-9999px;left:0;width:1100px;background:#fff;padding:40px;font-family:Inter,Arial,sans-serif'; |
| let itemsHtml=qd.items.map(it=>`<tr style="border-bottom:1px solid #e2e8f0"> |
| <td style="padding:10px 6px;text-align:center;font-size:13px">${it.stt}</td> |
| <td style="padding:10px 6px"><img src="${it.image}" style="width:80px;height:80px;object-fit:contain;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc" onerror="this.style.display='none'"></td> |
| <td style="padding:10px 6px;font-size:12px;font-weight:600">${it.name}</td> |
| <td style="padding:10px 6px;text-align:center;font-size:12px">${it.model}</td> |
| <td style="padding:10px 6px;font-size:11px;color:#64748b;max-width:180px">${it.specs}</td> |
| <td style="padding:10px 6px;text-align:center;font-size:13px">${it.qty}</td> |
| <td style="padding:10px 6px;text-align:right;font-size:12px">${fmtVND(it.price)}</td> |
| <td style="padding:10px 6px;text-align:right;font-size:12px;${it.discPrice<it.price?'color:#dc3545;font-weight:700':''}">${fmtVND(it.discPrice)}</td> |
| <td style="padding:10px 6px;text-align:right;font-size:13px;font-weight:700">${fmtVND(it.total)}</td> |
| <td style="padding:10px 6px;font-size:11px">${it.note}</td> |
| </tr>`).join(''); |
| |
| div.innerHTML=` |
| <div style="text-align:center;margin-bottom:20px"> |
| <img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" alt="V.AI STUDIO" style="height:40px;object-fit:contain"> |
| <div style="font-size:11px;color:#64748b;letter-spacing:2px;font-style:italic">❝ Niềm tin khách hàng là tài sản của chúng tôi ❞</div> |
| <div style="font-size:20px;font-weight:800;color:#db9815;margin-top:10px">BẢNG BÁO GIÁ</div> |
| </div> |
| <div style="display:flex;justify-content:space-between;margin-bottom:20px;font-size:12px;color:#0f172a"> |
| <div><strong>Khách hàng:</strong> ${qd.customer.name}<br><strong>SĐT:</strong> ${qd.customer.phone}<br><strong>Email:</strong> ${qd.customer.email}<br><strong>Địa chỉ:</strong> ${qd.customer.addr}</div> |
| <div style="text-align:right"><strong>Mã đơn hàng:</strong> ${qd.customer.orderCode}<br><strong>Ngày:</strong> ${qd.customer.date}<br><strong>Zalo:</strong> <a href="https://zalo.me/0981873395" target="_blank">0981 873 395</a></div> |
| </div> |
| <table style="width:100%;border-collapse:collapse"> |
| <thead><tr style="background:#003f62;color:#fff"> |
| <th style="padding:10px 6px;font-size:11px">STT</th><th style="padding:10px 6px;font-size:11px">Hình ảnh</th><th style="padding:10px 6px;font-size:11px;text-align:left">Tên sản phẩm</th><th style="padding:10px 6px;font-size:11px">Mã SP</th><th style="padding:10px 6px;font-size:11px">Thông tin SP</th><th style="padding:10px 6px;font-size:11px">SL</th><th style="padding:10px 6px;font-size:11px">Đơn giá</th><th style="padding:10px 6px;font-size:11px">Đơn giá CK</th><th style="padding:10px 6px;font-size:11px">Thành tiền</th><th style="padding:10px 6px;font-size:11px">Ghi chú</th> |
| </tr></thead> |
| <tbody>${itemsHtml}</tbody> |
| <tfoot><tr style="border-top:3px solid #003f62"> |
| <td colspan="8" style="padding:12px 6px;text-align:right;font-size:14px;font-weight:800;color:#003f62">TỔNG CỘNG:</td> |
| <td style="padding:12px 6px;text-align:right;font-size:15px;font-weight:900;color:#003f62">${fmtVND(qd.grandTotal)}</td><td></td> |
| </tr></tfoot> |
| </table> |
| <div style="margin-top:20px;font-size:11px;color:#64748b;font-style:italic"> |
| Miễn phí giao hàng trong TPHCM. | Giá đã bao gồm VAT (Hỗ trợ trị giá đơn hàng VAT xuất theo đúng số tiền khác mã). |
| </div>`; |
| |
| document.body.appendChild(div); |
| // Wait for images and fonts |
| await new Promise(r=>setTimeout(r,500)); |
| |
| try{ |
| let canvas=await html2canvas(div,{scale:2,useCORS:true,allowTaint:true,logging:false}); |
| let imgData=canvas.toDataURL('image/jpeg',0.95); |
| let {jsPDF}=window.jspdf; |
| let pdf=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'}); |
| let pdfW=pdf.internal.pageSize.getWidth(); |
| let pdfH=pdf.internal.pageSize.getHeight(); |
| let imgW=canvas.width;let imgH=canvas.height; |
| let ratio=pdfW/imgW*2;// scale:2 correction |
| let totalH=imgH*ratio/2; |
| let pageH=pdfH-10;let y=5;let srcY=0; |
| let sliceH=pageH/ratio*2; |
| // Multi-page support |
| while(srcY<imgH){ |
| if(srcY>0)pdf.addPage(); |
| let clipCanvas=document.createElement('canvas'); |
| clipCanvas.width=imgW;clipCanvas.height=Math.min(sliceH,imgH-srcY); |
| clipCanvas.getContext('2d').drawImage(canvas,0,srcY,imgW,clipCanvas.height,0,0,imgW,clipCanvas.height); |
| let sliceData=clipCanvas.toDataURL('image/jpeg',0.95); |
| let sliceRatio=pdfW/imgW; |
| pdf.addImage(sliceData,'JPEG',0,y,pdfW,clipCanvas.height*sliceRatio); |
| srcY+=sliceH; |
| } |
| pdf.save((qd.customer.orderCode||'BG')+'.pdf'); |
| showToast('Đã xuất file PDF!'); |
| }catch(e){console.error('PDF error:',e);showToast('Lỗi xuất PDF. Vui lòng thử lại.')} |
| div.remove(); |
| } |
| |
| function printQuotation(){ |
| exportPDF();// Reuse PDF then user can print from PDF viewer |
| } |
| |
| // Init on load |
| function init(){buildCats();buildPrices();buildBrands();render(); |
| // Try path-based routing first, then hash-based |
| if(!parseCurrentPath()){parseUrlParams();render();} |
| } |
| // Handle ?quote= parameter — auto-add products to cart + open quotation |
| (function(){ |
| let params = new URLSearchParams(location.search); |
| let quoteParam = params.get('quote'); |
| if(quoteParam){ |
| let skus = quoteParam.split(',').map(s=>s.trim()).filter(Boolean); |
| // Wait for products to load |
| let waitForD = setInterval(function(){ |
| if(typeof D !== 'undefined' && D.length > 0 && typeof addToCart === 'function'){ |
| clearInterval(waitForD); |
| skus.forEach(function(sku){ |
| let skuClean = sku.toLowerCase().replace(/[.\- ]/g,''); |
| let idx = D.findIndex(function(p){ |
| return (p.sku||'').toLowerCase().replace(/[.\- ]/g,'') === skuClean || |
| (p.model||'').toLowerCase().replace(/[.\- ]/g,'') === skuClean; |
| }); |
| if(idx >= 0) addToCart(idx); |
| }); |
| // Open quotation after adding |
| setTimeout(function(){ |
| if(typeof openQuotation === 'function') openQuotation(); |
| else if(typeof openCart === 'function') openCart(); |
| }, 500); |
| } |
| }, 500); |
| setTimeout(function(){ clearInterval(waitForD); }, 15000); |
| } |
| })(); |
| // === QUOTE LINK DECODER === |
| // Handles ?q=Base64 links from Zalo bot |
| (function(){ |
| var params=new URLSearchParams(location.search); |
| var q=params.get('q'); |
| if(!q)return; |
| try{ |
| // Decode Base64 (add padding back) |
| while(q.length%4)q+='='; |
| var decoded=atob(q.replace(/-/g,'+').replace(/_/g,'/')); |
| // Format: SKU:QTY:PRICE,SKU:QTY:PRICE |
| var items=decoded.split(','); |
| var waitD=setInterval(function(){ |
| if(typeof D==='undefined'||!D.length||typeof addToCart!=='function')return; |
| clearInterval(waitD); |
| items.forEach(function(item){ |
| var parts=item.split(':'); |
| var sku=parts[0],qty=parseInt(parts[1])||1,price=parseInt(parts[2])||0; |
| var skuClean=sku.toLowerCase().replace(/[.\- ]/g,''); |
| var idx=D.findIndex(function(p){ |
| return (p.sku||'').toLowerCase().replace(/[.\- ]/g,'')===skuClean|| |
| (p.model||'').toLowerCase().replace(/[.\- ]/g,'')===skuClean; |
| }); |
| if(idx>=0){ |
| addToCart(idx); |
| // Override price if discounted |
| if(price>0&&price!==D[idx].priceNum){ |
| var c=cart[cart.length-1]; |
| if(c)c.priceNum=price; |
| } |
| } |
| }); |
| setTimeout(function(){ |
| if(typeof openQuotation==='function')openQuotation(); |
| else if(typeof openCart==='function')openCart(); |
| },600); |
| },500); |
| setTimeout(function(){clearInterval(waitD)},15000); |
| }catch(e){console.error('Quote decode error:',e)} |
| })(); |
| // === ENCRYPTED QUOTE LINK DECODER v2 === |
| (function(){ |
| var KEY='vai2026studio'; |
| function xorDecrypt(encoded){ |
| while(encoded.length%4)encoded+='='; |
| var raw=atob(encoded.replace(/-/g,'+').replace(/_/g,'/')); |
| var result=''; |
| for(var i=0;i<raw.length;i++)result+=String.fromCharCode(raw.charCodeAt(i)^KEY.charCodeAt(i%KEY.length)); |
| return result; |
| } |
| var params=new URLSearchParams(location.search); |
| var qk=params.get('qk'); |
| var qd=params.get('qd'); |
| var encoded=qk||qd; |
| if(!encoded)return; |
| var isDiscounted=!!qd; |
| try{ |
| var decoded=xorDecrypt(encoded); |
| var items=decoded.split(','); |
| var waitD=setInterval(function(){ |
| if(typeof D==='undefined'||!D.length||typeof addToCart!=='function')return; |
| clearInterval(waitD); |
| // Clear cart first to avoid duplicates |
| if(typeof cart!=='undefined')cart.length=0; |
| items.forEach(function(item){ |
| var parts=item.split(':'); |
| var sku=parts[0],qty=parseInt(parts[1])||1,price=parseInt(parts[2])||0; |
| var skuClean=sku.toLowerCase().replace(/[.\- ]/g,''); |
| var idx=D.findIndex(function(p){ |
| return (p.sku||'').toLowerCase().replace(/[.\- ]/g,'')===skuClean||(p.model||'').toLowerCase().replace(/[.\- ]/g,'')===skuClean||(p.slug||'')===sku; |
| }); |
| if(idx>=0){ |
| addToCart(idx); |
| // Set correct quantity |
| var c=cart[cart.length-1]; |
| if(c)c.qty=qty; |
| } |
| }); |
| // Open quotation |
| setTimeout(function(){ |
| if(typeof openQuotation==='function')openQuotation(); |
| // If discounted, apply prices after table renders |
| if(isDiscounted){ |
| setTimeout(function(){ |
| items.forEach(function(item,i){ |
| var parts=item.split(':'); |
| var price=parseInt(parts[2])||0; |
| if(price>0){ |
| var discInput=document.querySelector('.qt-disc[data-idx="'+i+'"]'); |
| if(discInput){ |
| discInput.value=price.toLocaleString('vi-VN'); |
| discInput.dispatchEvent(new Event('input')); |
| } |
| } |
| }); |
| if(typeof updateQuoteTotal==='function')updateQuoteTotal(); |
| },800); |
| } |
| },600); |
| },500); |
| setTimeout(function(){clearInterval(waitD)},15000); |
| }catch(e){console.error('Quote decode:',e)} |
| })(); |
| document.addEventListener('DOMContentLoaded',()=>{ |
| initCatalogue(); |
| updateCartBadge(); |
| // Auto-open chat if ?chat=1 |
| if(location.search.includes('chat=1')){setTimeout(()=>toggleChat(),500)} |
| }); |
| function shareQuoteLink(){ |
| var KEY='vai2026studio'; |
| var parts=cart.map(function(c,i){ |
| var disc=document.querySelector('.qt-disc[data-idx="'+i+'"]'); |
| var price=disc?parseInt(disc.value.replace(/\D/g,''))||c.priceNum:c.priceNum; |
| return (c.sku||c.slug)+':'+c.qty+':'+price; |
| }); |
| var dataStr=parts.join(','); |
| var encrypted=''; |
| for(var i=0;i<dataStr.length;i++)encrypted+=String.fromCharCode(dataStr.charCodeAt(i)^KEY.charCodeAt(i%KEY.length)); |
| var encoded=btoa(encrypted).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''); |
| var link=location.origin+'/?qd='+encoded; |
| if(navigator.clipboard)navigator.clipboard.writeText(link); |
| prompt('🔗 Link báo giá (đã áp CK) — chia sẻ qua Zalo:',link); |
| showToast('✅ Đã copy link báo giá!'); |
| } |
| async function shareQuoteImage(){ |
| let qd=getQuoteData(); |
| // Build same HTML as exportPDF |
| let div=document.createElement('div'); |
| div.style.cssText='position:fixed;top:-9999px;left:0;width:1100px;background:#fff;padding:40px;font-family:Inter,Arial,sans-serif'; |
| let itemsHtml=qd.items.map(it=>`<tr style="border-bottom:1px solid #e2e8f0"> |
| <td style="padding:10px 6px;text-align:center;font-size:13px">${it.stt}</td> |
| <td style="padding:10px 6px"><img src="${it.image}" style="width:80px;height:80px;object-fit:contain;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc" onerror="this.style.display='none'"></td> |
| <td style="padding:10px 6px;font-size:12px;font-weight:600">${it.name}</td> |
| <td style="padding:10px 6px;text-align:center;font-size:12px">${it.model}</td> |
| <td style="padding:10px 6px;font-size:11px;color:#64748b;max-width:180px">${it.specs}</td> |
| <td style="padding:10px 6px;text-align:center;font-size:13px">${it.qty}</td> |
| <td style="padding:10px 6px;text-align:right;font-size:12px">${fmtVND(it.price)}</td> |
| <td style="padding:10px 6px;text-align:right;font-size:12px;${it.discPrice<it.price?'color:#dc3545;font-weight:700':''}">${fmtVND(it.discPrice)}</td> |
| <td style="padding:10px 6px;text-align:right;font-size:13px;font-weight:700">${fmtVND(it.total)}</td> |
| <td style="padding:10px 6px;font-size:11px">${it.note}</td> |
| </tr>`).join(''); |
| |
| div.innerHTML=` |
| <div style="text-align:left;margin-bottom:20px"> |
| <img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" style="height:40px;object-fit:contain;margin:0 auto"> |
| <div style="font-size:11px;color:#64748b;font-style:italic">❝ Niềm tin khách hàng là tài sản của chúng tôi ❞</div> |
| <div style="font-size:20px;font-weight:800;color:#db9815;margin-top:10px">BẢNG BÁO GIÁ</div> |
| </div> |
| <div style="display:flex;justify-content:space-between;margin-bottom:20px;font-size:12px"> |
| <div><strong>Khách hàng:</strong> ${qd.customer.name}<br><strong>SĐT:</strong> ${qd.customer.phone}<br><strong>Email:</strong> ${qd.customer.email}<br><strong>Địa chỉ:</strong> ${qd.customer.addr}</div> |
| <div style="text-align:right"><strong>Mã đơn hàng:</strong> ${qd.customer.orderCode}<br><strong>Ngày:</strong> ${qd.customer.date}<br><strong>Zalo:</strong> 0981 873 395</div> |
| </div> |
| <table style="width:100%;border-collapse:collapse"> |
| <thead><tr style="background:#003f62;color:#fff"> |
| <th style="padding:10px 6px;font-size:11px">STT</th><th style="padding:10px 6px;font-size:11px">Hình</th><th style="padding:10px 6px;font-size:11px;text-align:left">Tên SP</th><th style="padding:10px 6px;font-size:11px">Mã</th><th style="padding:10px 6px;font-size:11px">Thông tin</th><th style="padding:10px 6px;font-size:11px">SL</th><th style="padding:10px 6px;font-size:11px">Đơn giá</th><th style="padding:10px 6px;font-size:11px">Đơn giá CK</th><th style="padding:10px 6px;font-size:11px">Thành tiền</th><th style="padding:10px 6px;font-size:11px">Ghi chú</th> |
| </tr></thead> |
| <tbody>${itemsHtml}</tbody> |
| <tfoot><tr style="border-top:3px solid #003f62"><td colspan="8" style="padding:12px;text-align:right;font-size:14px;font-weight:800;color:#003f62">TỔNG CỘNG:</td><td style="padding:12px;text-align:right;font-size:15px;font-weight:900;color:#003f62">${fmtVND(qd.grandTotal)}</td><td></td></tr></tfoot> |
| </table> |
| <div style="margin-top:16px;font-size:11px;color:#64748b;font-style:italic"> |
| Miễn phí giao hàng trong TPHCM. | Giá đã bao gồm VAT. |
| </div>`; |
| |
| document.body.appendChild(div); |
| await new Promise(r=>setTimeout(r,500)); |
| |
| try{ |
| let canvas=await html2canvas(div,{scale:2,useCORS:true,allowTaint:true,backgroundColor:'#fff'}); |
| div.remove(); |
| let blob=await new Promise(r=>canvas.toBlob(r,'image/png')); |
| let file=new File([blob],(qd.customer.orderCode||'BG')+'.png',{type:'image/png'}); |
| |
| // Try native share (mobile) |
| if(navigator.share&&navigator.canShare&&navigator.canShare({files:[file]})){ |
| await navigator.share({title:'Báo giá V.AI STUDIO - '+(qd.customer.orderCode||''),files:[file]}); |
| }else{ |
| // Fallback: show preview + download |
| let url=URL.createObjectURL(blob); |
| let preview=document.createElement('div'); |
| preview.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:999;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(4px)'; |
| preview.innerHTML='<div style="background:#fff;border-radius:16px;max-width:90vw;max-height:85vh;overflow:auto;padding:16px"><img src="'+url+'" style="max-width:100%;border-radius:8px"><div style="display:flex;gap:8px;margin-top:12px;justify-content:center"><a href="'+url+'" download="'+(qd.customer.orderCode||'BG')+'.png" style="padding:10px 20px;background:#003f62;color:#fff;border-radius:8px;font-weight:700;font-size:.85rem;text-decoration:none"><i class="fas fa-download"></i> Tải ảnh</a><button onclick="this.closest(\'div[style*=fixed]\').remove()" style="padding:10px 20px;background:#e2e8f0;border:none;border-radius:8px;font-weight:700;font-size:.85rem;cursor:pointer">Đóng</button></div></div>'; |
| document.body.appendChild(preview); |
| showToast('✅ Ảnh báo giá đã sẵn sàng! Tải về rồi gửi qua Zalo.'); |
| } |
| }catch(e){ |
| div.remove(); |
| showToast('Lỗi: '+e.message); |
| } |
| } |
| |
| </script> |
|
|
| |
| <a href="https://zalo.me/0981873395" target="_blank" title="Chat Zalo" style="position:fixed;bottom:90px;right:24px;z-index:180;width:50px;height:50px;border-radius:50%;background:#0068FF;color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.3rem;box-shadow:0 4px 16px rgba(0,104,255,.4);text-decoration:none;transition:all .25s ease"><i class="fas fa-comment-dots"></i></a> |
| <button class="chat-fab" onclick="toggleChat()" title="Tư Vấn AI"><i class="fas fa-robot"></i></button> |
| <div class="chat-fab-label">Tư Vấn AI</div> |
| <div class="chat-overlay2" id="chatOverlay2" onclick="closeChat()"></div> |
| <div class="chatbox" id="chatbox"> |
| <div class="chat-head"> |
| <div class="chat-head-avatar"><i class="fas fa-robot"></i></div> |
| <div class="chat-head-info"><h4>Tư Vấn AI</h4><span>Trợ lý thiết bị nhà bếp thông minh</span></div> |
| <button class="chat-close" onclick="closeChat()" title="Đóng"><i class="fas fa-times"></i></button> |
| </div> |
| <div class="chat-body" id="chatBody"> |
| <div class="chat-msg bot">Xin chào anh/chị! 👋 Em là <strong>V.AI STUDIO</strong> — trợ lý tư vấn thiết bị nhà bếp.<br><br>Em có thể giúp anh/chị:<br>🔍 Tìm & so sánh sản phẩm phù hợp<br>💡 Tư vấn theo nhu cầu & ngân sách<br>📋 Giải đáp thông số kỹ thuật<br><br>Anh/chị cần tư vấn gì ạ?<div style="margin-top:8px;padding-top:6px;border-top:1px solid #e2e8f0"><button onclick="shareChatToZalo(this.closest('.chat-msg'))" style="padding:5px 12px;background:#0068FF;color:#fff;border:none;border-radius:6px;font-size:.72rem;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:5px"><i class="fas fa-comment-dots"></i> Gửi qua Zalo</button></div></div> |
| </div> |
| <div class="chat-suggestions" id="chatSuggestions"> |
| <div class="chat-sug" onclick="sendSuggestion(this)">Tư vấn bếp từ cho gia đình</div> |
| <div class="chat-sug" onclick="sendSuggestion(this)">Máy hút mùi nào tốt?</div> |
| <div class="chat-sug" onclick="sendSuggestion(this)">Khóa cửa thông minh</div> |
| <div class="chat-sug" onclick="sendSuggestion(this)">📋 Lên báo giá</div> |
| <div class="chat-sug" onclick="sendSuggestion(this)">🛒 Giỏ hàng</div> |
| </div> |
| <div class="chat-foot"> |
| <textarea class="chat-input" id="chatInput" placeholder="Nhập câu hỏi..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}"></textarea> |
| <button class="chat-send" id="chatSend" onclick="sendChat()"><i class="fas fa-paper-plane"></i></button> |
| </div> |
| </div> |
|
|
| |
| <div class="share-popover-overlay" id="shareOverlay" onclick="if(event.target===this)closeSharePopover()"> |
| <div class="share-popover" style="position:relative"> |
| <button class="share-close-btn" onclick="closeSharePopover()"><i class="fas fa-times"></i></button> |
| <h4><i class="fas fa-share-alt"></i> Chia sẻ</h4> |
| <div class="share-apps"> |
| <div class="share-app" onclick="shareVia('facebook')"><div class="share-app-icon" style="background:#1877F2"><i class="fab fa-facebook-f"></i></div><span>Facebook</span></div> |
| <div class="share-app" onclick="shareVia('messenger')"><div class="share-app-icon" style="background:#0099FF"><i class="fab fa-facebook-messenger"></i></div><span>Messenger</span></div> |
| <div class="share-app" onclick="shareVia('zalo')"><div class="share-app-icon" style="background:#0068FF"><i class="fas fa-comment-dots"></i></div><span>Zalo</span></div> |
| <div class="share-app" onclick="shareVia('twitter')"><div class="share-app-icon" style="background:#000"><i class="fab fa-x-twitter"></i></div><span>X</span></div> |
| <div class="share-app" onclick="shareVia('telegram')"><div class="share-app-icon" style="background:#2AABEE"><i class="fab fa-telegram-plane"></i></div><span>Telegram</span></div> |
| <div class="share-app" onclick="shareVia('whatsapp')"><div class="share-app-icon" style="background:#25D366"><i class="fab fa-whatsapp"></i></div><span>WhatsApp</span></div> |
| <div class="share-app" onclick="shareVia('email')"><div class="share-app-icon" style="background:var(--g)"><i class="fas fa-envelope"></i></div><span>Email</span></div> |
| <div class="share-app" onclick="shareVia('copy')"><div class="share-app-icon" style="background:var(--p)"><i class="fas fa-link"></i></div><span>Copy link</span></div> |
| </div> |
| <div class="share-link-box"> |
| <input class="share-link-input" id="shareLinkInput" readonly> |
| <button class="share-link-copy" onclick="shareVia('copy')"><i class="fas fa-copy"></i> Sao chép</button> |
| </div> |
| </div> |
| </div> |
| <script src="search-plus-boot.js?v=20"></script> |
| <script src="shorts-subtitles.js?v=2"></script> |
| <script src="ancuong-shorts.js?v=8"></script> |
| <script src="custom-shorts.js?v=4"></script> |
| <script src="smart-kitchen-shorts.js?v=4"></script> |
|
|
| <script>function doAISearch(){ |
| |
| if(window._vaiSearchContext && typeof D!=='undefined' && D.length>0){ |
| var input=document.getElementById('aiSearch');var resultsDiv=document.getElementById('aiResults'); |
| if(!input||!resultsDiv)return;var query=input.value.trim(); |
| if(!query||query.length<2){resultsDiv.style.display='block';resultsDiv.innerHTML='💡 Nhập mã SP hoặc mô tả: "bếp từ Grob dưới 10tr", "máy hút mùi Malloca"';return;} |
| resultsDiv.style.display='block';resultsDiv.innerHTML='⏳ Đang tìm...'; |
| |
| var queries=query.split(/[,;]+/).map(function(c){return c.trim()}).filter(function(c){return c.length>=2}); |
| if(queries.length>1){ |
| var allResults=[]; |
| queries.forEach(function(q2){ |
| var qLow=q2.toLowerCase().replace(/[.\-_ \/]/g,''); |
| for(var i=0;i<D.length&&allResults.length<20;i++){ |
| var p=D[i];if(!p)continue; |
| var fields=[(p.sku||''),(p.mod||''),(p.slug||'')].map(function(f){return f.toLowerCase().replace(/[.\-_ \/]/g,'')}); |
| if(fields.some(function(f){return f===qLow||(f&&qLow.length>=3&&(f.indexOf(qLow)!==-1||qLow.indexOf(f)!==-1))})){allResults.push(p);} |
| } |
| }); |
| if(allResults.length){_renderAIResults(allResults,resultsDiv);return;} |
| } |
| |
| var results=window._vaiSearchContext(query,24); |
| if(!results.length){resultsDiv.innerHTML='❌ Không tìm thấy "'+query+'". Thử: "bếp từ Malloca", "máy hút mùi dưới 15tr"';return;} |
| var prods=results.map(function(r){return r.p;}); |
| _renderAIResults(prods,resultsDiv); |
| return; |
| } |
| |
| var q2=document.getElementById('aiSearch').value.trim(); |
| if(!q2){document.getElementById('aiResults').innerHTML='💡 Nhập mã hoặc tên SP';document.getElementById('aiResults').style.display='block';return;} |
| document.getElementById('aiResults').innerHTML='⏳ Đang tải dữ liệu...';document.getElementById('aiResults').style.display='block'; |
| } |
| |
| window._vaiAddOrder=function(slug,btn){ |
| if(!slug||typeof D==='undefined')return; |
| for(var i=0;i<D.length;i++){ |
| if(D[i]&&D[i].slug===slug){ |
| if(window._showOrderPicker){ |
| window._showOrderPicker(D[i],function(r){ |
| if(r==='ok'){btn.textContent='✓';btn.style.background='#059669';} |
| else if(r==='dup'){btn.textContent='Có rồi';btn.style.background='#94a3b8';} |
| setTimeout(function(){btn.textContent='+ Đơn';btn.style.background='#003f62';},2500); |
| }); |
| } |
| break; |
| } |
| } |
| }; |
| function _renderAIResults(prods,res){ |
| var fmt=function(n){if(!n||isNaN(n))return'LH';return Number(n).toLocaleString('vi-VN')+'đ'}; |
| var cat=window._vaiDetectCategory?window._vaiDetectCategory(document.getElementById('aiSearch').value):''; |
| var brand=window._vaiDetectBrand?window._vaiDetectBrand(document.getElementById('aiSearch').value):''; |
| var parts=[]; |
| if(cat)parts.push('📂 '+cat);if(brand)parts.push('🏷 '+brand); |
| parts.push('🔍 '+prods.length+' SP'); |
| var h='<div style="margin-bottom:10px;display:flex;gap:6px;flex-wrap:wrap">'+parts.map(function(p){return'<span style="background:#eff6ff;padding:3px 8px;border-radius:20px;font-size:11px;font-weight:600;color:#1e40af;border:1px solid #bfdbfe">'+p+'</span>'}).join('')+'</div>'; |
| h+='<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px">'; |
| prods.slice(0,24).forEach(function(p){ |
| var idx=D.indexOf(p); |
| var name=p.name||p.n||'';var price=p.price||p.p||'LH';var img=p.image||p.img||p.i||'';var sku=p.sku||p.model||p.mod||'';var brand2=p.brand||'';var slug=p.slug||''; |
| var feats=(p.feats||[]).slice(0,2).join(' • '); |
| var specs=p.specs||{};var sk=Object.entries(specs).slice(0,3); |
| h+='<div style="background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden;transition:all .2s;cursor:pointer" onclick="showDetail('+idx+')" onmouseover="this.style.transform=\'translateY(-2px)\';this.style.boxShadow=\'0 4px 12px rgba(0,0,0,.1)\'" onmouseout="this.style.transform=\'\';this.style.boxShadow=\'\'">'; |
| if(img)h+='<div style="height:90px;background:#f8fafc;display:flex;align-items:center;justify-content:center"><img src="'+img+'" style="max-width:100%;max-height:85px;object-fit:contain" onerror="this.parentElement.style.display=\'none\'"></div>'; |
| h+='<div style="padding:8px"><div style="font-size:11px;font-weight:600;color:#003f62;line-height:1.3;height:30px;overflow:hidden">'+name.substring(0,55)+'</div>'; |
| if(sku)h+='<div style="font-size:9px;color:#94a3b8;margin-top:2px">'+sku+'</div>'; |
| if(brand2)h+='<div style="font-size:9px;color:#64748b;margin-top:1px">'+brand2+'</div>'; |
| if(feats)h+='<div style="font-size:8.5px;color:#64748b;margin-top:2px;height:12px;overflow:hidden">'+feats+'</div>'; |
| if(sk.length){h+='<div style="margin-top:3px;font-size:8px;color:#555;border-top:1px solid #f0f0f0;padding-top:3px">';sk.forEach(function(kv){h+=kv[0]+': <b>'+kv[1]+'</b><br>'});h+='</div>'} |
| h+='<div style="font-size:13px;font-weight:800;color:#059669;margin-top:4px">'+price+'</div>'; |
| h+='</div>'; |
| |
| h+='<div style="display:flex;gap:4px;padding:0 6px 6px">'; |
| h+='<button onclick="event.stopPropagation();if(typeof addToCart===\'function\')addToCart('+idx+')" style="flex:1;padding:4px;background:#db9815;color:#fff;border:none;border-radius:5px;font-size:9px;font-weight:700;cursor:pointer">+ Giỏ</button>'; |
| h+='<button onclick="event.stopPropagation();window._vaiAddOrder(\''+slug+'\',this)" style="flex:1;padding:4px;background:#003f62;color:#fff;border:none;border-radius:5px;font-size:9px;font-weight:700;cursor:pointer">+ Đơn</button>'; |
| h+='</div></div>'; |
| }); |
| h+='</div>';res.innerHTML=h; |
| } |
| |
| async function _aiSelectProducts(q,res){ |
| try{ |
| if(typeof D==='undefined'||!D.length){res.innerHTML='⏳ Đang tải SP...';return} |
| |
| const bm=q.match(/(\d+)\s*(tr|triệu|m)/i); |
| const budget=bm?parseInt(bm[1])*1000000:50000000; |
| |
| const typeMap=[{k:['bep tu','bep dien','bep'],n:'Bếp từ'},{k:['may hut','hut mui'],n:'Máy hút mùi'},{k:['chau rua','bon rua'],n:'Chậu rửa'},{k:['voi rua'],n:'Vòi rửa'},{k:['lo nuong'],n:'Lò nướng'}]; |
| const qn=q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); |
| let wantTypes=typeMap.filter(t=>t.k.some(k=>qn.includes(k))); |
| if(!wantTypes.length)wantTypes=typeMap.slice(0,3); |
| const perBudget=Math.floor(budget/wantTypes.length); |
| |
| let catalog=[]; |
| wantTypes.forEach(typ=>{ |
| let items=D.filter(p=>{ |
| const pn=((p.name||'')+(p.cat||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); |
| return typ.k.some(k=>pn.includes(k))&&p.priceNum>0&&p.priceNum<=perBudget*1.5; |
| }); |
| |
| for(let i=items.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[items[i],items[j]]=[items[j],items[i]]} |
| |
| let m=items.filter(p=>/malloca/i.test(p.brand)); |
| let g=items.filter(p=>/grob/i.test(p.brand)); |
| let o=items.filter(p=>!/malloca|grob/i.test(p.brand)); |
| let pool=[...m.slice(0,5),...g.slice(0,4),...o.slice(0,3)]; |
| pool.forEach(p=>catalog.push({sku:p.sku,name:p.name.substring(0,50),brand:p.brand,price:p.priceNum,type:typ.n})); |
| }); |
| |
| const aiPrompt=`Khách yêu cầu: "${q}"\nNgân sách: ${budget.toLocaleString('vi')}đ\nDanh sách SP có sẵn:\n${catalog.map((p,i)=>i+1+'. ['+p.sku+'] '+p.name+' | '+p.brand+' | '+p.price.toLocaleString('vi')+'đ | Loại: '+p.type).join('\n')}\n\nChọn combo TỐT NHẤT (1 SP mỗi loại, ưu tiên Malloca/Grob, tổng ≤ ngân sách). Trả lời CHỈ JSON: {"picks":[{"sku":"...","reason":"lý do chọn ngắn"}]}`; |
| const aiRes=await fetch('https://router.huggingface.co/v1/chat/completions',{ |
| method:'POST', |
| headers:{'Content-Type':'application/json','Authorization':'Bearer '+document.querySelector('meta[name=hf-token]')?.content||''}, |
| body:JSON.stringify({model:'Qwen/Qwen2.5-72B-Instruct',messages:[{role:'user',content:aiPrompt}],max_tokens:300,temperature:0.7}) |
| }); |
| if(!aiRes.ok){ |
| |
| _doQuoteFallback(wantTypes,perBudget,budget,res);return; |
| } |
| const aiData=await aiRes.json(); |
| const content=aiData.choices[0].message.content; |
| const jsonMatch=content.match(/\{[\s\S]*\}/); |
| if(!jsonMatch){_doQuoteFallback(wantTypes,perBudget,budget,res);return} |
| const result=JSON.parse(jsonMatch[0]); |
| const selectedSkus=result.picks.map(p=>p.sku); |
| const reasons=Object.fromEntries(result.picks.map(p=>[p.sku,p.reason||''])); |
| |
| let picks=[];let total=0; |
| selectedSkus.forEach(sku=>{ |
| const found=D.find(p=>p.sku===sku); |
| if(found){ |
| const typ=catalog.find(c=>c.sku===sku); |
| picks.push({...found,typeName:typ?typ.type:'SP',reason:reasons[sku]||''}); |
| total+=found.priceNum; |
| } |
| }); |
| if(!picks.length){_doQuoteFallback(wantTypes,perBudget,budget,res);return} |
| _renderQuote(picks,total,budget,res,true); |
| }catch(e){ |
| console.error(e); |
| |
| const bm=q.match(/(\d+)\s*(tr|triệu|m)/i); |
| const budget=bm?parseInt(bm[1])*1000000:50000000; |
| const typeMap=[{k:['bep tu','bep dien','bep'],n:'Bếp từ'},{k:['may hut','hut mui'],n:'Máy hút mùi'},{k:['chau rua','bon rua'],n:'Chậu rửa'}]; |
| const qn=q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); |
| let wantTypes=typeMap.filter(t=>t.k.some(k=>qn.includes(k))); |
| if(!wantTypes.length)wantTypes=typeMap.slice(0,3); |
| _doQuoteFallback(wantTypes,Math.floor(budget/wantTypes.length),budget,res); |
| } |
| } |
| function _doQuoteFallback(wantTypes,perBudget,budget,res){ |
| let picks=[];let total=0; |
| function shuffle(a){for(let i=a.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a} |
| wantTypes.forEach(typ=>{ |
| let all=D.filter(p=>{ |
| const pn=((p.name||'')+(p.cat||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); |
| return typ.k.some(k=>pn.includes(k))&&p.priceNum>0&&p.priceNum<=perBudget*1.5; |
| }); |
| let m=shuffle(all.filter(p=>/malloca/i.test(p.brand))); |
| let g=shuffle(all.filter(p=>/grob/i.test(p.brand))); |
| let pool=[...m.slice(0,4),...g.slice(0,3),...shuffle(all).slice(0,3)]; |
| shuffle(pool); |
| let pick=pool.find(p=>p.priceNum<=perBudget*1.3)||pool[0]; |
| if(pick){picks.push({...pick,typeName:typ.n,reason:''});total+=pick.priceNum} |
| }); |
| if(!picks.length){res.innerHTML='😅 Không đủ SP phù hợp';return} |
| _renderQuote(picks,total,budget,res,false); |
| } |
| function _renderQuote(picks,total,budget,res,isAI){ |
| let h='<div style="font-weight:700;margin-bottom:10px;font-size:.95rem">'+(isAI?'🤖':'📋')+' AI BÁO GIÁ <span style="color:#64748b;font-weight:400">(~'+budget.toLocaleString('vi')+'đ)</span></div>'; |
| h+='<table style="width:100%;border-collapse:collapse;font-size:.8rem">'; |
| h+='<tr style="background:#003f62;color:#fff"><th style="padding:8px;text-align:left">Loại</th><th style="text-align:left">Sản phẩm</th><th>Brand</th><th style="text-align:right;padding-right:8px">Giá</th></tr>'; |
| picks.forEach((p,i)=>{ |
| h+='<tr style="border-bottom:1px solid #e2e8f0;'+(i%2?'background:#f8fafc':'')+'"><td style="padding:8px;font-weight:600">'+p.typeName+'</td><td>'+p.name.substring(0,38)+(p.reason?'<br><span style="font-size:.65rem;color:#28a745">💡 '+p.reason+'</span>':'')+'<br><span style="font-size:.62rem;color:#888">'+p.sku+(p.specs&&Object.keys(p.specs).length?' | '+Object.entries(p.specs).slice(0,2).map(([k,v])=>k+':'+v).join(', '):'')+'</span></td><td style="text-align:center;font-size:.75rem">'+p.brand+'</td><td style="text-align:right;font-weight:700;color:#003f62;padding-right:8px">'+(p.price||'LH')+'</td></tr>'; |
| }); |
| h+='<tr style="background:#003f62;color:#fff;font-weight:800"><td colspan="3" style="padding:10px;text-align:right">TỔNG:</td><td style="text-align:right;padding-right:8px;font-size:.95rem">'+total.toLocaleString('vi')+'đ</td></tr></table>'; |
| const diff=budget-total; |
| h+=diff>=0?'<div style="margin-top:8px;color:#28a745;font-weight:600;font-size:.82rem">✅ Trong ngân sách! Dư '+diff.toLocaleString('vi')+'đ</div>':'<div style="margin-top:8px;color:#dc3545;font-weight:600;font-size:.82rem">⚠️ Vượt '+(0-diff).toLocaleString('vi')+'đ</div>'; |
| h+='<div style="margin-top:12px;text-align:center;display:flex;gap:8px;justify-content:center;flex-wrap:wrap">'; |
| h+=''; |
| h+='<button onclick="_addQuotePicks()" style="padding:9px 18px;background:#db9815;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:.82rem">🛒 Thêm giỏ</button></div>'; |
| res.innerHTML=h;window._quotePicks=picks; |
| } |
| |
| </script> |
|
|
| <script> |
| |
| (function(){ |
| |
| const origChatSend=window.sendChatMessage; |
| if(typeof origChatSend==='function'){ |
| window.sendChatMessage=function(msg){ |
| if(_handleChatAI(msg))return; |
| origChatSend(msg); |
| } |
| } |
| |
| window.chatAI=function(msg){_handleChatAI(msg)}; |
| })(); |
| function _handleChatAI(msg){ |
| if(!msg||typeof D==='undefined')return false; |
| const lo=msg.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); |
| |
| const isSearch=lo.includes('tim')||lo.includes('kiem'); |
| |
| const isBaoGia=lo.includes('bao gia')||lo.includes('combo')||lo.includes('du toan'); |
| if(isBaoGia)return false; |
| if(isSearch){ |
| document.getElementById('aiSearch').value=msg; |
| doAISearch(); |
| document.getElementById('aiResults').scrollIntoView({behavior:'smooth',block:'start'}); |
| return true; |
| } |
| return false; |
| } |
| </script> |
| <script> |
| |
| |
| function _tryChatAI(msg){ |
| var lo=msg.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/\u0111/g,'d'); |
| |
| if(lo.includes('bao gia')||lo.includes('combo')||lo.includes('du toan'))return false; |
| return false; |
| } |
| </script> |
| <script> |
| |
| |
| (function(){ |
| var origOpen = window.openQuoteModal || window.showQuoteModal; |
| |
| var fixQuoteImages = function(){ |
| setTimeout(function(){ |
| var rows = document.querySelectorAll('.quote-table tr'); |
| var cart = window.CART || []; |
| rows.forEach(function(row, i){ |
| if(i === 0) return; |
| var imgCell = row.querySelector('.qt-img, img'); |
| var cartItem = cart[i-1]; |
| if(cartItem && !imgCell){ |
| |
| var firstTd = row.querySelector('td'); |
| if(firstTd && cartItem.image){ |
| firstTd.innerHTML = '<img class="qt-img" src="'+cartItem.image+'" style="width:60px;height:60px;object-fit:contain;border-radius:4px">'; |
| } |
| } else if(imgCell && cartItem && cartItem.image && imgCell.tagName === 'IMG' && !imgCell.src){ |
| imgCell.src = cartItem.image; |
| } |
| }); |
| }, 200); |
| }; |
| |
| var origOpenCart = window.openCheckout || window.openQuote; |
| if(origOpenCart){ |
| var wrapped = function(){ |
| origOpenCart.apply(this, arguments); |
| fixQuoteImages(); |
| }; |
| if(window.openCheckout) window.openCheckout = wrapped; |
| if(window.openQuote) window.openQuote = wrapped; |
| } |
| document.addEventListener('click', function(e){ |
| if(e.target && (e.target.textContent||'').includes('Báo giá')){ |
| fixQuoteImages(); |
| } |
| }); |
| })(); |
| |
| |
| (function(){ |
| document.addEventListener('input', function(e){ |
| if(e.target && e.target.classList.contains('qt-disc')){ |
| var row = e.target.closest('tr'); |
| if(!row) return; |
| var ckVal = parseFloat(e.target.value) || 0; |
| |
| var cells = row.querySelectorAll('td'); |
| var priceCell = null; |
| var ckPriceCell = null; |
| var ttCell = null; |
| var qtyCell = null; |
| cells.forEach(function(td, idx){ |
| var text = td.textContent.replace(/[^\d]/g,''); |
| if(td.querySelector('.qt-disc')) return; |
| if(td.classList.contains('qt-price') || (text.length > 4 && !priceCell && idx > 2)){ |
| if(!priceCell) priceCell = td; |
| else if(!ckPriceCell) ckPriceCell = td; |
| else if(!ttCell) ttCell = td; |
| } |
| }); |
| |
| var origPrice = parseInt((row.dataset.price || (priceCell?priceCell.textContent:'')).replace(/[^\d]/g,'')) || 0; |
| var qty = parseInt(row.dataset.qty || '1') || 1; |
| if(origPrice > 0 && ckVal >= 0){ |
| var discPrice = Math.round(origPrice * (1 - ckVal/100)); |
| var thanhTien = discPrice * qty; |
| |
| if(ckPriceCell) ckPriceCell.textContent = discPrice.toLocaleString('vi-VN'); |
| if(ttCell) ttCell.textContent = thanhTien.toLocaleString('vi-VN'); |
| |
| var totalEl = document.querySelector('.quote-total-row td:last-child') || document.querySelector('[class*="quote-total"]'); |
| if(totalEl){ |
| var allTT = 0; |
| document.querySelectorAll('.quote-table tr').forEach(function(r,i){ |
| if(i===0)return; |
| var tds = r.querySelectorAll('td'); |
| var lastNum = tds[tds.length-1]; |
| if(lastNum) allTT += parseInt(lastNum.textContent.replace(/[^\d]/g,''))||0; |
| }); |
| totalEl.textContent = allTT.toLocaleString('vi-VN') + ' VNĐ'; |
| } |
| } |
| } |
| }); |
| })(); |
| |
| |
| (function(){ |
| var origExport = window.exportQuoteExcel || window.downloadExcel; |
| window.exportQuoteExcel = window.downloadExcel = function(){ |
| |
| if(typeof ExcelJS === 'undefined'){ |
| if(origExport) return origExport.apply(this, arguments); |
| alert('Excel library not loaded'); |
| return; |
| } |
| var wb = new ExcelJS.Workbook(); |
| var ws = wb.addWorksheet('Bao Gia'); |
| |
| ws.columns = [ |
| {header:'STT', key:'stt', width:5}, |
| {header:'Ma SP', key:'sku', width:15}, |
| {header:'Ten SP', key:'name', width:40}, |
| {header:'DVT', key:'dvt', width:6}, |
| {header:'SL', key:'qty', width:5}, |
| {header:'Don gia', key:'price', width:15}, |
| {header:'CK %', key:'ck', width:8}, |
| {header:'Don gia CK', key:'ckprice', width:15}, |
| {header:'Thanh tien', key:'total', width:15}, |
| ]; |
| |
| ws.getRow(1).font = {bold: true}; |
| ws.getRow(1).fill = {type:'pattern', pattern:'solid', fgColor:{argb:'FF1F4E79'}}; |
| ws.getRow(1).font = {bold:true, color:{argb:'FFFFFFFF'}}; |
| |
| var rows = document.querySelectorAll('.quote-table tr'); |
| var grandTotal = 0; |
| rows.forEach(function(row, i){ |
| if(i===0) return; |
| if(row.classList.contains('quote-total-row')) return; |
| var cells = row.querySelectorAll('td'); |
| var ckInput = row.querySelector('.qt-disc'); |
| var ckVal = ckInput ? (parseFloat(ckInput.value)||0) : 0; |
| |
| var texts = Array.from(cells).map(function(c){return c.textContent.trim()}); |
| var price = parseInt((row.dataset.price || texts[5] || '0').replace(/[^\d]/g,'')) || 0; |
| var qty = parseInt(row.dataset.qty || texts[4] || '1') || 1; |
| var ckPrice = Math.round(price * (1 - ckVal/100)); |
| var tt = ckPrice * qty; |
| grandTotal += tt; |
| var dataRow = ws.addRow({ |
| stt: i, |
| sku: texts[1] || '', |
| name: texts[2] || '', |
| dvt: 'Bộ', |
| qty: qty, |
| price: price, |
| ck: ckVal, |
| ckprice: ckPrice, |
| total: tt |
| }); |
| |
| dataRow.getCell('price').numFmt = '#,##0'; |
| dataRow.getCell('ckprice').numFmt = '#,##0'; |
| dataRow.getCell('total').numFmt = '#,##0'; |
| }); |
| |
| var totalRow = ws.addRow({name:'TONG CONG', total: grandTotal}); |
| totalRow.font = {bold:true}; |
| totalRow.getCell('total').numFmt = '#,##0'; |
| |
| var dataStart = 2; |
| var dataEnd = ws.rowCount - 1; |
| for(var r=dataStart; r<=dataEnd; r++){ |
| ws.getCell('H'+r).value = {formula: 'F'+r+'*(1-G'+r+'/100)'}; |
| ws.getCell('I'+r).value = {formula: 'H'+r+'*E'+r}; |
| } |
| ws.getCell('I'+(dataEnd+1)).value = {formula: 'SUM(I'+dataStart+':I'+dataEnd+')'}; |
| |
| wb.xlsx.writeBuffer().then(function(buf){ |
| var blob = new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); |
| var url = URL.createObjectURL(blob); |
| var a = document.createElement('a'); |
| a.href = url; |
| a.download = 'BaoGia_VAISTUDIO_' + new Date().toISOString().slice(0,10) + '.xlsx'; |
| a.click(); |
| URL.revokeObjectURL(url); |
| }); |
| }; |
| })(); |
| </script> |
|
|
|
|
|
|
| <script> |
| if(localStorage.getItem('vas_quote_unlocked')==='1')document.body.classList.add('vas-unlocked'); |
| </script> |
|
|
|
|
|
|
| <script> |
| |
| function getSavedOrders(){ |
| try{return JSON.parse(localStorage.getItem('vas_orders')||'[]')}catch(e){return[]} |
| } |
| function saveSavedOrders(orders){ |
| localStorage.setItem('vas_orders',JSON.stringify(orders)); |
| } |
| |
| function saveToOrder(idx){ |
| if(typeof D==='undefined'||!D[idx])return; |
| var p=D[idx]; |
| |
| var existing=document.getElementById('orderSelectPopup'); |
| if(existing)existing.remove(); |
| |
| var orders=getSavedOrders(); |
| var popup=document.createElement('div'); |
| popup.id='orderSelectPopup'; |
| popup.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px'; |
| |
| var content='<div style="background:#fff;border-radius:12px;padding:20px;max-width:360px;width:100%;box-shadow:0 8px 30px rgba(0,0,0,.2)">'; |
| content+='<h3 style="margin-bottom:12px;font-size:.95rem;color:#003f62">Chon don hang</h3>'; |
| content+='<div style="font-size:.78rem;color:#666;margin-bottom:10px">SP: <b>'+((p.name||'').split('|').pop()||p.sku||'').trim().substring(0,40)+'</b></div>'; |
| |
| |
| if(orders.length>0){ |
| content+='<div style="max-height:200px;overflow-y:auto;margin-bottom:10px">'; |
| orders.forEach(function(ord,i){ |
| content+='<div onclick="addToSelectedOrder('+idx+','+i+')" style="padding:8px 12px;border:1.5px solid #ddd;border-radius:6px;margin-bottom:6px;cursor:pointer;font-size:.8rem;transition:.2s" onmouseover="this.style.borderColor=\'#003f62\'" onmouseout="this.style.borderColor=\'#ddd\'">'; |
| content+='<b>'+ord.name+'</b> <span style="color:#888">('+ord.items.length+' SP)</span>'; |
| content+='</div>'; |
| }); |
| content+='</div>'; |
| }else{ |
| content+='<div style="color:#888;font-size:.8rem;margin-bottom:10px">Chua co don hang nao.</div>'; |
| } |
| |
| |
| content+='<div style="display:flex;gap:8px">'; |
| content+='<button onclick="createNewOrderAndAdd('+idx+')" style="flex:1;padding:8px;background:#003f62;color:#fff;border:none;border-radius:6px;font-size:.8rem;cursor:pointer;font-weight:600">+ Don moi</button>'; |
| content+='<button onclick="document.getElementById(\'orderSelectPopup\').remove()" style="padding:8px 14px;background:#eee;border:none;border-radius:6px;font-size:.8rem;cursor:pointer">Huy</button>'; |
| content+='</div></div>'; |
| |
| popup.innerHTML=content; |
| popup.onclick=function(e){if(e.target===popup)popup.remove()}; |
| document.body.appendChild(popup); |
| } |
| |
| function createNewOrderAndAdd(idx){ |
| var name=prompt('Ten don hang moi:','Don '+new Date().toLocaleDateString('vi')); |
| if(!name)return; |
| var orders=getSavedOrders(); |
| var p=D[idx]; |
| orders.push({name:name,items:[{sku:p.sku||p.model||'',name:p.name||'',price:p.priceNum||0,qty:1}],created:new Date().toISOString()}); |
| saveSavedOrders(orders); |
| document.getElementById('orderSelectPopup').remove(); |
| showToast('Da tao don "'+name+'" va them SP!'); |
| } |
| |
| function addToSelectedOrder(idx,orderIdx){ |
| var orders=getSavedOrders(); |
| var p=D[idx]; |
| if(orders[orderIdx]){ |
| orders[orderIdx].items.push({sku:p.sku||p.model||'',name:p.name||'',price:p.priceNum||0,qty:1}); |
| saveSavedOrders(orders); |
| document.getElementById('orderSelectPopup').remove(); |
| showToast('Da them vao don "'+orders[orderIdx].name+'"!'); |
| } |
| } |
| |
| function showToast(msg){ |
| var t=document.createElement('div'); |
| t.style.cssText='position:fixed;top:20px;right:20px;background:#28a745;color:#fff;padding:10px 16px;border-radius:8px;z-index:99999;font-size:.82rem;box-shadow:0 4px 12px rgba(0,0,0,.2)'; |
| t.textContent=msg; |
| document.body.appendChild(t); |
| setTimeout(function(){t.remove()},2500); |
| } |
| </script> |
| <script src="malloca-shorts.js?v=3"></script> |
| <script src="ancuong.js?v=5"></script> |
| </body> |
| </html> |