/** * V.AI STUDIO — Excel Multi-Download Complete Fix * ============================================ * FIX TRIỆT ĐỐI cho vấn đề: File Excel chỉ tải được 1 lần * * Áp dụng cho: * - Modal "Lập báo giá" -> nút 💾 Excel * - Modal "Đơn hàng" -> nút 💾 Lưu, GH Excel */ (function() { 'use strict'; // Registry quản lý blob URLs var blobRegistry = []; var isUnloading = false; // Cleanup khi tab đóng function cleanupAllBlobs() { isUnloading = true; blobRegistry.forEach(function(entry) { if (!entry.revoked && entry.url) { try { URL.revokeObjectURL(entry.url); entry.revoked = true; } catch (e) {} } }); blobRegistry = []; } window.addEventListener('pagehide', cleanupAllBlobs); window.addEventListener('beforeunload', cleanupAllBlobs); window.addEventListener('unload', cleanupAllBlobs); // PATCH URL.revokeObjectURL - không revoke blob URLs khi chưa unload (function() { var origRevoke = URL.revokeObjectURL; URL.revokeObjectURL = function(url) { if (isUnloading || !url || !url.startsWith('blob:')) { return origRevoke.apply(this, arguments); } // Chỉ đăng ký, không revoke var found = blobRegistry.find(function(e) { return e.url === url; }); if (!found) { blobRegistry.push({ url: url, revoked: false, created: Date.now() }); } }; })(); // PATCH exportExcel - Báo giá var origExportExcel = window.exportExcel; window.exportExcel = async function() { if (typeof ExcelJS === 'undefined') { if (typeof showToast === 'function') showToast('⚠️ Chưa có ExcelJS'); return; } // Lấy dữ liệu var d, qd, code, qrUrl; if (window.VAI_QR && typeof window.VAI_QR.getData === 'function') { d = window.VAI_QR.getData(); qd = d.qd; code = window.VAI_QR.getEffectiveOrderCode(); qrUrl = window.VAI_QR.getQRUrl(d.remaining || d.grandTotal, code); } else if (window._origGetQuoteData) { qd = window._origGetQuoteData(); code = 'VAS-' + Date.now(); d = { grandTotal: qd.grandTotal || 0, remaining: qd.grandTotal || 0 }; } else { if (typeof showToast === 'function') showToast('⚠️ Lỗi dữ liệu'); return; } // TẠO MỚI hoàn toàn var wb = new ExcelJS.Workbook(); var ws = wb.addWorksheet('Báo giá'); ws.views = [{ showGridLines: false }]; ws.columns = [{ width: 5 }, { width: 34 }, { width: 18 }, { width: 10 }, { width: 15 }]; // Header ws.mergeCells('A1:E1'); ws.getCell('A1').value = 'BẢNG BÁO GIÁ V.AI STUDIO'; ws.getCell('A1').font = { bold: true, size: 16, color: { argb: 'FF003F62' } }; ws.getCell('A1').alignment = { horizontal: 'center', vertical: 'middle' }; ws.getCell('A1').fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF0F7FF' } }; // Customer info ws.getCell('A2').value = 'Khách hàng:'; ws.mergeCells('B2:D2'); ws.getCell('B2').value = (qd.customer ? qd.customer.name : '') || ''; ws.getCell('A3').value = 'SĐT:'; ws.getCell('B3').value = (qd.customer ? qd.customer.phone : '') || ''; ws.getCell('D3').value = 'Mã đơn: ' + code; ws.getCell('A4').value = 'Địa chỉ:'; ws.mergeCells('B4:E4'); ws.getCell('B4').value = (qd.customer ? qd.customer.addr : '') || ''; // Header row var hr = ws.getRow(6); ['STT', 'Tên sản phẩm', 'Mã SP', 'SL', 'Giá'].forEach(function(h, i) { var c = hr.getCell(i + 1); c.value = h; c.font = { bold: true, color: { argb: 'FFFFFFFF' } }; c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } }; }); // Items var cr = 7, total = 0; (qd.items || []).forEach(function(it, i) { var row = ws.getRow(cr); row.values = [i + 1, it.name || '', it.model || '', it.qty || 1, (it.discPrice || it.price || 0) * (it.qty || 1)]; if ((it.discPrice || it.price) > 0) { row.getCell(5).numFmt = '#,##0"đ"'; } row.getCell(5).font = { bold: true }; total += (it.discPrice || it.price || 0) * (it.qty || 1); cr++; }); // Total ws.mergeCells(cr, 1, cr, 4); ws.getCell('A' + cr).value = 'TỔNG CỘNG'; ws.getCell('A' + cr).font = { bold: true, size: 12 }; ws.getCell('E' + cr).value = total; ws.getCell('E' + cr).font = { bold: true, size: 14, color: { argb: 'FF003F62' } }; ws.getCell('E' + cr).numFmt = '#,##0"đ"'; // Download - KHÔNG revoke var buf = await wb.xlsx.writeBuffer(); var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); var url = URL.createObjectURL(blob); // Đăng ký để cleanup khi unload blobRegistry.push({ url: url, revoked: false, created: Date.now() }); var a = document.createElement('a'); a.href = url; a.download = code + '.xlsx'; document.body.appendChild(a); a.click(); setTimeout(function() { document.body.removeChild(a); }, 100); if (typeof showToast === 'function') { showToast('✅ Đã tải ' + code + '.xlsx'); } }; // PATCH exportDeliveryExcel - Giao hàng var origExportDeliveryExcel = window.exportDeliveryExcel; window.exportDeliveryExcel = async function(order, opt) { if (typeof ExcelJS === 'undefined') { if (typeof showToast === 'function') showToast('⚠️ Chưa có ExcelJS'); return; } // Lấy dữ liệu qd var qd; if (order && order.items) { qd = { customer: { name: order.customer || '', phone: order.phone || '', addr: order.addr || '', date: order.date || '' }, items: (order.items || []).map(function(it, i) { return { stt: i + 1, name: it.name || '', model: it.model || '', qty: it.qty || 1, note: it.note || '' }; }) }; } else if (window._origGetQuoteData) { qd = window._origGetQuoteData(); } else if (window.VAI_QR && window.VAI_QR.getData) { qd = window.VAI_QR.getData().qd; } else { if (typeof showToast === 'function') showToast('⚠️ Lỗi dữ liệu'); return; } var code = (order && order.code) ? order.code : (window.VAI_QR ? window.VAI_QR.getEffectiveOrderCode() : 'VAS-GH'); // TẠO MỚI hoàn toàn var wb = new ExcelJS.Workbook(); var ws = wb.addWorksheet('Giao hàng'); ws.views = [{ showGridLines: false }]; ws.columns = [{ width: 5 }, { width: 34 }, { width: 14 }, { width: 8 }, { width: 20 }]; // Header ws.mergeCells('A1:E1'); ws.getCell('A1').value = 'PHIẾU GIAO HÀNG'; ws.getCell('A1').font = { bold: true, size: 16, color: { argb: 'FF003F62' } }; ws.getCell('A1').alignment = { horizontal: 'center' }; ws.getCell('A1').fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF0F7FF' } }; // Customer info ws.getCell('A2').value = 'KH: ' + (qd.customer ? qd.customer.name : ''); ws.getCell('D2').value = 'Mã: ' + code; ws.getCell('A3').value = 'SĐT: ' + (qd.customer ? qd.customer.phone : ''); ws.getCell('D3').value = 'Ngày: ' + (qd.customer ? (qd.customer.date || '') : ''); ws.getCell('A4').value = 'Địa chỉ: ' + (qd.customer ? (qd.customer.addr || '') : ''); // Header row var hr = ws.getRow(6); ['STT', 'Tên sản phẩm', 'Mã SP', 'SL', 'Ghi chú'].forEach(function(h, i) { var c = hr.getCell(i + 1); c.value = h; c.font = { bold: true, color: { argb: 'FFFFFFFF' } }; c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } }; }); // Items var cr = 7, totalQty = 0; (qd.items || []).forEach(function(it, i) { var row = ws.getRow(cr); row.values = [i + 1, it.name || '', it.model || '', it.qty || 1, it.note || '']; totalQty += (it.qty || 1); cr++; }); // Total var tr = ws.getRow(cr); ws.mergeCells(cr, 1, cr, 3); ws.getCell('A' + cr).value = 'TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D' + cr).value = totalQty; // Signature cr += 2; ws.getCell('A' + cr).value = 'Bên giao: ________________'; ws.getCell('D' + cr).value = 'Bên nhận: ________________'; // Download - KHÔNG revoke var buf = await wb.xlsx.writeBuffer(); var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); var url = URL.createObjectURL(blob); // Đăng ký để cleanup khi unload blobRegistry.push({ url: url, revoked: false, created: Date.now() }); var a = document.createElement('a'); a.href = url; a.download = 'GH-' + code + '.xlsx'; document.body.appendChild(a); a.click(); setTimeout(function() { document.body.removeChild(a); }, 100); if (typeof showToast === 'function') { showToast('✅ Đã tải GH-' + code + '.xlsx'); } }; console.log('[Excel Multi-Fix Complete] Loaded - Supports unlimited downloads!'); })();