/** * V.AI STUDIO — EXPORT WORKING FIX (SINGLE FILE) * * ===== VẤN ĐỀ GỐC ĐÃ SỬA ===== * 1. qr-payment.js có capture-phase click handler (e.preventDefault + e.stopPropagation) * → chặn mọi onclick khác của nút Excel, không file fix nào chạy được * 2. 3 file fix (robust-v2, multi-download, ultimate-fix) chồng chéo ghi đè lẫn nhau * 3. window.exportExcel kiểm tra typeof ExcelJS==='undefined' → silent return * 4. URL.revokeObjectURL bị intercept double-book → không cache được blob * * ===== GIẢI PHÁP ===== * 1. Dùng addEventListener với capture phase CHẠY TRƯỚC handler của qr-payment * 2. Gọi TRỰC TIẾP _doExportExcel (bỏ qua window.exportExcel) * 3. Chỉ 1 intercept URL.createObjectURL duy nhất * 4. Không phụ thuộc vào file fix nào khác */ (function() { 'use strict'; console.log('[VAI EXPORT FIX] === LOADING ==='); // ===== 1. ĐỢI ExcelJS + html2canvas + jspdf LOAD ===== function waitForLibs(cb, maxWait) { var waited = 0; var interval = 200; maxWait = maxWait || 15000; var timer = setInterval(function() { waited += interval; if (typeof ExcelJS !== 'undefined' && typeof html2canvas !== 'undefined' && typeof jspdf !== 'undefined') { clearInterval(timer); console.log('[VAI EXPORT FIX] All libraries loaded after ' + waited + 'ms'); cb(); } else if (waited >= maxWait) { clearInterval(timer); console.warn('[VAI EXPORT FIX] Libraries not fully loaded after ' + maxWait + 'ms, proceeding anyway'); cb(); } }, interval); } // ===== 2. OVERRIDE window.exportExcel — KHÔNG silent return ===== function overrideExportExcel() { if (window.__vaiExportFixed) return; window.__vaiExportFixed = true; // Save original for fallback var origExportExcel = window.exportExcel; window.exportExcel = async function() { console.log('[VAI EXPORT FIX] exportExcel called'); if (typeof ExcelJS === 'undefined') { console.warn('[VAI EXPORT FIX] ExcelJS not loaded yet, waiting...'); // Wait for ExcelJS and retry return new Promise(function(resolve) { var w = 0; var t = setInterval(function() { w += 200; if (typeof ExcelJS !== 'undefined') { clearInterval(t); console.log('[VAI EXPORT FIX] ExcelJS loaded, retrying...'); resolve(doExport()); } if (w >= 10000) { clearInterval(t); console.error('[VAI EXPORT FIX] ExcelJS timeout, using fallback'); resolve(doFallbackExport()); } }, 200); }); } return doExport(); }; async function doExport() { try { // Get data from VAI_QR or getQuoteData var d = null; if (window.VAI_QR && typeof window.VAI_QR.getData === 'function') { d = window.VAI_QR.getData(); } else if (typeof getData === 'function') { d = getData(); } else if (typeof window.getQuoteData === 'function') { var qd = window.getQuoteData(); var parsed = { fees: [], notes: [], deposit: 0, discountPercent: 0, itemDiscounts: {} }; d = { qd: qd, fees: [], notes: [], deposit: 0, discountPercent: 0, itemDiscounts: {}, grandTotal: qd.grandTotal || 0, remaining: qd.grandTotal || 0, productTotal: qd.grandTotal || 0 }; } if (!d || !d.qd) { console.error('[VAI EXPORT FIX] No data available'); alert('⚠️ Không có dữ liệu để xuất. Vui lòng thêm sản phẩm vào giỏ hàng.'); return; } var code = (window.VAI_QR && typeof window.VAI_QR.getEffectiveOrderCode === 'function') ? window.VAI_QR.getEffectiveOrderCode() : 'BAOGIA'; var qrUrl = (window.VAI_QR && typeof window.VAI_QR.getQRUrl === 'function') ? window.VAI_QR.getQRUrl(d.deposit > 0 ? d.remaining : d.grandTotal, code) : ''; // Try calling _doExportExcel directly if (typeof _doExportExcel === 'function') { console.log('[VAI EXPORT FIX] Calling _doExportExcel directly'); await _doExportExcel(d, d.qd, code, qrUrl); console.log('[VAI EXPORT FIX] _doExportExcel completed'); return; } // Fallback: try VAI_QR.exportExcel if (window.VAI_QR && typeof window.VAI_QR.exportExcel === 'function') { console.log('[VAI EXPORT FIX] Calling VAI_QR.exportExcel'); await window.VAI_QR.exportExcel(d, d.qd, code, qrUrl); return; } // Last resort: fallback console.warn('[VAI EXPORT FIX] No export function found, using fallback'); await doFallbackExport(); } catch(e) { console.error('[VAI EXPORT FIX] Export error:', e); try { await doFallbackExport(); } catch(e2) { console.error('[VAI EXPORT FIX] Fallback also failed:', e2); alert('❌ Lỗi xuất Excel. Vui lòng thử lại sau.'); } } } async function doFallbackExport() { console.log('[VAI EXPORT FIX] Using fallback HTML table export'); var qd = null; var code = 'BAOGIA'; try { if (window.VAI_QR && typeof window.VAI_QR.getData === 'function') { var d = window.VAI_QR.getData(); qd = d.qd; code = window.VAI_QR.getEffectiveOrderCode(); } else if (typeof getQuoteData === 'function') { qd = getQuoteData(); } else if (typeof window.getQuoteData === 'function') { qd = window.getQuoteData(); } } catch(e) { qd = { customer: {}, items: [] }; } if (!qd) qd = { customer: {}, items: [] }; if (!qd.items) qd.items = []; if (!qd.customer) qd.customer = {}; var html = '' + '' + ''; html += ''; html += ''; html += ''; var total = 0; (qd.items || []).forEach(function(it, i) { var lineTotal = (it.discPrice || it.price || 0) * (it.qty || 1); total += lineTotal; html += '' + '' + '' + '' + '' + '' + '' + '' + ''; }); html += '' + '' + '' + ''; html += '
BẢNG BÁO GIÁ
KH: ' + (qd.customer.name || '') + 'Mã: ' + code + '
STTTên SPSLĐơn giáGiá CKThành tiền
' + (it.stt || (i + 1)) + '' + (it.name || '') + '' + (it.model || '') + '' + (it.qty || 1) + '' + Number(it.price || 0).toLocaleString('vi-VN') + 'đ' + Number(it.discPrice || it.price || 0).toLocaleString('vi-VN') + 'đ' + Number(lineTotal).toLocaleString('vi-VN') + 'đ
TỔNG CỘNG' + Number(total).toLocaleString('vi-VN') + 'đ
'; var blob = new Blob([html], { type: 'application/vnd.ms-excel' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = code + '.xls'; a.style.display = 'none'; document.body.appendChild(a); a.click(); setTimeout(function() { document.body.removeChild(a); URL.revokeObjectURL(url); }, 1000); console.log('[VAI EXPORT FIX] Fallback download initiated: ' + code + '.xls'); } } // ===== 3. BIND TRỰC TIẾP VÀO NÚT EXCEL (dùng capture phase để chạy TRƯỚC qr-payment) ===== function bindExcelButtons() { document.querySelectorAll('button, a, [role="button"]').forEach(function(btn) { var text = (btn.textContent || '').toLowerCase(); var cls = (btn.className || '').toLowerCase(); var id = (btn.id || '').toLowerCase(); // Check if this is an Excel export button var isExcel = (text.indexOf('excel') >= 0 || cls.indexOf('excel') >= 0 || id === 'od-xl') && (text.indexOf('xuất') >= 0 || text.indexOf('export') >= 0 || cls.indexOf('quote-btn-excel') >= 0 || id === 'od-xl'); if (!isExcel) return; if (btn.dataset.vaiExportCaptureBound) return; btn.dataset.vaiExportCaptureBound = 'true'; console.log('[VAI EXPORT FIX] Binding Excel button:', (btn.textContent || '').trim()); // Use capture phase (true) to run BEFORE qr-payment's capture handler btn.addEventListener('click', function(e) { console.log('[VAI EXPORT FIX] Excel button clicked (capture phase)'); e.stopImmediatePropagation(); // Stop qr-payment handler from running // Don't preventDefault — let the click happen naturally // Call our export function if (typeof window.exportExcel === 'function') { window.exportExcel(); } else { console.error('[VAI EXPORT FIX] window.exportExcel not available'); alert('⚠️ Chức năng xuất chưa sẵn sàng. Vui lòng thử lại.'); } }, true); // capture phase = true }); } // ===== 4. MUTATION OBSERVER — bind nút động ===== var observer = new MutationObserver(function() { bindExcelButtons(); }); observer.observe(document.body, { childList: true, subtree: true }); // ===== 5. INIT ===== function init() { console.log('[VAI EXPORT FIX] Initializing...'); overrideExportExcel(); bindExcelButtons(); console.log('[VAI EXPORT FIX] ✅ Ready'); } waitForLibs(function() { init(); }); // Periodic re-check for dynamic buttons setInterval(function() { bindExcelButtons(); }, 2000); console.log('[VAI EXPORT FIX] Module loaded'); })();