Tạo file excel-fix-v2.js hoàn chỉnh + cập nhật index.html

#12
by bep40 - opened
Files changed (2) hide show
  1. excel-fix-v2.js +448 -0
  2. index.html +3 -3
excel-fix-v2.js ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO - Excel Export Fix Ultimate v2.0
3
+ * ============================================
4
+ * FIX 100%: Multi-download Excel for Quote & Order modals
5
+ *
6
+ * Key fixes:
7
+ * 1. Global blob URL registry - prevents premature URL.revokeObjectURL
8
+ * 2. Creates fresh blob for each download
9
+ * 3. Proper cleanup on page unload only
10
+ * 4. Click protection to prevent double-clicks
11
+ */
12
+ (function(){
13
+ 'use strict';
14
+
15
+ // ===== GLOBAL STATE =====
16
+ window.__VAI_EXCEL_FIX = window.__VAI_EXCEL_FIX || {
17
+ blobRegistry: [],
18
+ isUnloading: false
19
+ };
20
+
21
+ var g = window.__VAI_EXCEL_FIX;
22
+
23
+ // ===== CLEANUP ON UNLOAD =====
24
+ function cleanupAllBlobs() {
25
+ g.isUnloading = true;
26
+ g.blobRegistry.forEach(function(entry) {
27
+ if (!entry.revoked && entry.url) {
28
+ try { URL.revokeObjectURL(entry.url); entry.revoked = true; } catch(e) {}
29
+ }
30
+ });
31
+ g.blobRegistry = [];
32
+ }
33
+
34
+ window.addEventListener('pagehide', cleanupAllBlobs);
35
+ window.addEventListener('beforeunload', cleanupAllBlobs);
36
+ window.addEventListener('unload', cleanupAllBlobs);
37
+
38
+ // ===== PATCH URL.revokeObjectURL - CRITICAL =====
39
+ if (!window.__VAI_REVOKE_PATCHED) {
40
+ var originalRevoke = URL.revokeObjectURL;
41
+ URL.revokeObjectURL = function(url) {
42
+ // Only revoke on unload or non-blob URLs
43
+ if (g.isUnloading || !url || typeof url !== 'string' || !url.startsWith('blob:')) {
44
+ return originalRevoke.call(this, url);
45
+ }
46
+ // Register but DON'T revoke - this is the key fix for multi-download
47
+ var exists = g.blobRegistry.find(function(e) { return e.url === url; });
48
+ if (!exists) {
49
+ g.blobRegistry.push({ url: url, revoked: false, created: Date.now() });
50
+ }
51
+ return undefined;
52
+ };
53
+ window.__VAI_REVOKE_PATCHED = true;
54
+ }
55
+
56
+ // ===== UTILITIES =====
57
+ function fmt(n) {
58
+ if (!n || isNaN(n)) return '0đ';
59
+ return Number(n).toLocaleString('vi-VN') + 'đ';
60
+ }
61
+
62
+ function stripDiacritics(s) {
63
+ return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[Đđ]/g, 'D');
64
+ }
65
+
66
+ function generateOrderCode(name) {
67
+ var d = new Date();
68
+ var dd = String(d.getDate()).padStart(2, '0');
69
+ var mm = String(d.getMonth() + 1).padStart(2, '0');
70
+ var yy = String(d.getFullYear()).slice(-2);
71
+ var ini = '';
72
+ if (name && name.trim()) {
73
+ name.trim().split(/\s+/).forEach(function(w) {
74
+ if (w) {
75
+ var ch = stripDiacritics(w.charAt(0)).toUpperCase();
76
+ if (/[A-Z]/.test(ch)) {
77
+ ini += ch;
78
+ }
79
+ }
80
+ });
81
+ }
82
+ return 'VAS' + (ini || 'X') + dd + mm + yy;
83
+ }
84
+
85
+ // ===== BUTTON STATE =====
86
+ function setBtnState(btn, exporting) {
87
+ if (!btn) return;
88
+ if (exporting) {
89
+ btn.disabled = true;
90
+ btn.dataset.vaiExporting = '1';
91
+ btn.dataset.vaiOrigHTML = btn.innerHTML;
92
+ btn.innerHTML = '<span style="opacity:0.7">⏳ Đang xuất...</span>';
93
+ } else {
94
+ btn.disabled = false;
95
+ btn.dataset.vaiExporting = '0';
96
+ if (btn.dataset.vaiOrigHTML) {
97
+ btn.innerHTML = btn.dataset.vaiOrigHTML;
98
+ }
99
+ }
100
+ }
101
+
102
+ // ===== CREATE DOWNLOAD =====
103
+ function downloadBlob(blob, filename) {
104
+ var url = URL.createObjectURL(blob);
105
+ g.blobRegistry.push({ url: url, revoked: false });
106
+
107
+ var a = document.createElement('a');
108
+ a.href = url;
109
+ a.download = filename;
110
+ a.style.cssText = 'position:fixed;left:-10000px;top:-10000px;width:1px;height:1px;';
111
+ document.body.appendChild(a);
112
+ a.click();
113
+
114
+ setTimeout(function() {
115
+ try { document.body.removeChild(a); } catch(e) {}
116
+ }, 100);
117
+ }
118
+
119
+ // ===== GET QUOTE DATA FROM MODAL =====
120
+ function getQuoteData() {
121
+ var data = {
122
+ customer: { name: '', phone: '', email: '', addr: '', date: new Date().toLocaleDateString('vi-VN') },
123
+ items: [],
124
+ grandTotal: 0
125
+ };
126
+
127
+ // Get cart data
128
+ var cart = window.cart || [];
129
+ var products = window.D || [];
130
+
131
+ cart.forEach(function(item, i) {
132
+ var product = products[item.idx] || (products.find ? products.find(function(p) { return p.id === item.id; }) : null);
133
+ var price = item.priceNum || item.price || (product ? product.price : 0) || 0;
134
+ data.items.push({
135
+ stt: i + 1,
136
+ image: product ? product.img : '',
137
+ name: product ? (product.name || product.n || '') : (item.name || ''),
138
+ model: product ? (product.sku || product.mod || '') : (item.sku || item.model || ''),
139
+ specs: product ? (product.info || product.desc || '') : '',
140
+ qty: item.qty || 1,
141
+ price: price,
142
+ discPrice: price,
143
+ total: (item.qty || 1) * price,
144
+ note: ''
145
+ });
146
+ });
147
+
148
+ // Get customer info from modal
149
+ var nameSelectors = ['#quoteName', '#quoteCustomerName', '#checkoutName'];
150
+ for (var j = 0; j < nameSelectors.length; j++) {
151
+ var el = document.querySelector(nameSelectors[j]);
152
+ if (el && el.value && el.value.trim()) {
153
+ data.customer.name = el.value.trim();
154
+ break;
155
+ }
156
+ }
157
+
158
+ var phoneEl = document.querySelector('#quotePhone, #checkoutPhone');
159
+ if (phoneEl && phoneEl.value) data.customer.phone = phoneEl.value.trim();
160
+
161
+ var emailEl = document.querySelector('#quoteEmail');
162
+ if (emailEl && emailEl.value) data.customer.email = emailEl.value.trim();
163
+
164
+ var addrEl = document.querySelector('#quoteAddr, #checkoutAddr');
165
+ if (addrEl && addrEl.value) data.customer.addr = addrEl.value.trim();
166
+
167
+ data.grandTotal = data.items.reduce(function(s, i) { return s + (i.total || 0); }, 0);
168
+ return data;
169
+ }
170
+
171
+ // ===== EXPORT QUOTE EXCEL =====
172
+ async function exportQuoteExcel(quoteData, code) {
173
+ if (typeof ExcelJS === 'undefined') {
174
+ if (typeof showToast === 'function') showToast('⚠️ Chưa có thư viện ExcelJS');
175
+ return false;
176
+ }
177
+
178
+ try {
179
+ var wb = new ExcelJS.Workbook();
180
+ var ws = wb.addWorksheet('Báo giá');
181
+ ws.views = [{ showGridLines: false }];
182
+ ws.columns = [
183
+ { width: 5 }, { width: 11 }, { width: 28 },
184
+ { width: 13 }, { width: 20 }, { width: 6 },
185
+ { width: 13 }, { width: 13 }, { width: 15 }, { width: 14 }
186
+ ];
187
+
188
+ ws.mergeCells('A3:J3');
189
+ ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
190
+ ws.getCell('A3').font = { bold: true, size: 18, color: { argb: 'FFDB9815' } };
191
+ ws.getCell('A3').alignment = { horizontal: 'center' };
192
+
193
+ ws.getCell('A5').value = 'Khách hàng:';
194
+ ws.mergeCells('B5:E5');
195
+ ws.getCell('B5').value = quoteData.customer?.name || '';
196
+
197
+ ws.getCell('H5').value = 'Mã đơn:';
198
+ ws.mergeCells('I5:J5');
199
+ ws.getCell('I5').value = code;
200
+
201
+ ws.getCell('A6').value = 'SĐT:';
202
+ ws.mergeCells('B6:E6');
203
+ ws.getCell('B6').value = quoteData.customer?.phone || '';
204
+
205
+ ws.getCell('H6').value = 'Ngày:';
206
+ ws.mergeCells('I6:J6');
207
+ ws.getCell('I6').value = quoteData.customer?.date || '';
208
+
209
+ ws.getCell('A7').value = 'Email:';
210
+ ws.mergeCells('B7:E7');
211
+ ws.getCell('B7').value = quoteData.customer?.email || '';
212
+
213
+ ws.getCell('A8').value = 'Địa chỉ:';
214
+ ws.mergeCells('B8:J8');
215
+ ws.getCell('B8').value = quoteData.customer?.addr || '';
216
+
217
+ var hr = ws.getRow(10);
218
+ ['STT', 'Hình', 'Tên sản phẩm', 'Mã SP', 'Thông tin', 'SL', 'Đơn giá', 'Giá CK', 'Thành tiền', 'Ghi chú'].forEach(function(h, i) {
219
+ var c = hr.getCell(i + 1);
220
+ c.value = h;
221
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
222
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
223
+ });
224
+
225
+ var cr = 11, firstRow = cr;
226
+ (quoteData.items || []).forEach(function(it, i) {
227
+ var row = ws.getRow(cr);
228
+ row.height = 50;
229
+ var bg = i % 2 === 0 ? 'FFF8FAFC' : 'FFFFFFFF';
230
+
231
+ row.getCell(1).value = it.stt || i + 1;
232
+ row.getCell(3).value = it.name || '';
233
+ row.getCell(3).font = { bold: true, size: 9 };
234
+ row.getCell(4).value = it.model || '';
235
+ row.getCell(5).value = it.specs || '';
236
+ row.getCell(5).font = { size: 8, color: { argb: 'FF64748B' } };
237
+ row.getCell(6).value = Number(it.qty || 1);
238
+ row.getCell(6).numFmt = '#,##0';
239
+ row.getCell(7).value = Number(it.price || 0);
240
+ row.getCell(7).numFmt = '#,##0"đ"';
241
+ row.getCell(8).value = Number(it.discPrice || it.price || 0);
242
+ row.getCell(8).numFmt = '#,##0"đ"';
243
+ if ((it.price || 0) > (it.discPrice || 0)) {
244
+ row.getCell(8).font = { bold: true, color: { argb: 'FFDC3545' } };
245
+ }
246
+ row.getCell(9).value = Number(it.total || 0);
247
+ row.getCell(9).numFmt = '#,##0"đ"';
248
+ row.getCell(9).font = { bold: true };
249
+ row.getCell(10).value = it.note || '';
250
+
251
+ cr++;
252
+ });
253
+
254
+ ws.getRow(cr).height = 28;
255
+ ws.mergeCells(cr, 1, cr, 5);
256
+ ws.getCell('A' + cr).value = 'TỔNG CỘNG';
257
+ ws.getCell('A' + cr).font = { bold: true, size: 13, color: { argb: 'FFFFFFFF' } };
258
+ ws.getCell('A' + cr).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
259
+ ws.getCell('E' + cr).value = { formula: 'SUM(I' + firstRow + ':I' + (cr - 1) + ')', result: Number(quoteData.grandTotal || 0) };
260
+ ws.getCell('E' + cr).numFmt = '#,##0"đ"';
261
+ ws.getCell('E' + cr).font = { bold: true, size: 14, color: { argb: 'FFF0B840' } };
262
+
263
+ wb.calcProperties.fullCalcOnLoad = true;
264
+
265
+ var buf = await wb.xlsx.writeBuffer();
266
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
267
+
268
+ downloadBlob(blob, code + '.xlsx');
269
+ return true;
270
+
271
+ } catch (err) {
272
+ console.error('[VAI Excel Fix] exportQuoteExcel error:', err);
273
+ alert('❌ Lỗi: ' + err.message);
274
+ return false;
275
+ }
276
+ }
277
+
278
+ // ===== EXPORT DELIVERY EXCEL =====
279
+ async function exportDeliveryExcel(quoteData, code) {
280
+ if (typeof ExcelJS === 'undefined') {
281
+ if (typeof showToast === 'function') showToast('⚠️ Chưa có thư viện ExcelJS');
282
+ return false;
283
+ }
284
+
285
+ try {
286
+ var wb = new ExcelJS.Workbook();
287
+ var ws = wb.addWorksheet('Giao hàng');
288
+ ws.views = [{ showGridLines: false }];
289
+ ws.columns = [
290
+ { width: 5 }, { width: 34 }, { width: 14 }, { width: 8 }, { width: 20 }
291
+ ];
292
+
293
+ ws.mergeCells('A1:E1');
294
+ ws.getCell('A1').value = 'PHIẾU GIAO HÀNG';
295
+ ws.getCell('A1').font = { bold: true, size: 16, color: { argb: 'FF003F62' } };
296
+ ws.getCell('A1').alignment = { horizontal: 'center' };
297
+
298
+ ws.getCell('A2').value = 'KH: ' + (quoteData.customer?.name || '');
299
+ ws.getCell('D2').value = 'Mã: ' + code;
300
+ ws.getCell('A3').value = 'SĐT: ' + (quoteData.customer?.phone || '');
301
+ ws.getCell('D3').value = 'Ngày: ' + (quoteData.customer?.date || '');
302
+ ws.getCell('A4').value = 'Địa chỉ: ' + (quoteData.customer?.addr || '');
303
+
304
+ var hr = ws.getRow(6);
305
+ ['STT', 'Tên sản phẩm', 'Mã SP', 'SL', 'Ghi chú'].forEach(function(h, i) {
306
+ var c = hr.getCell(i + 1);
307
+ c.value = h;
308
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
309
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
310
+ });
311
+
312
+ var cr = 7, totalQty = 0;
313
+ (quoteData.items || []).forEach(function(it, i) {
314
+ var row = ws.getRow(cr);
315
+ row.height = 20;
316
+ row.getCell(1).value = it.stt || i + 1;
317
+ row.getCell(2).value = it.name || '';
318
+ row.getCell(3).value = it.model || '';
319
+ row.getCell(4).value = it.qty || 1;
320
+ row.getCell(5).value = it.note || '';
321
+ totalQty += Number(it.qty || 1);
322
+ cr++;
323
+ });
324
+
325
+ ws.mergeCells(cr, 1, cr, 3);
326
+ ws.getCell('A' + cr).value = 'TỔNG CỘNG SỐ LƯỢNG';
327
+ ws.getCell('D' + cr).value = totalQty;
328
+
329
+ var buf = await wb.xlsx.writeBuffer();
330
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
331
+
332
+ downloadBlob(blob, 'GH-' + code + '.xlsx');
333
+ return true;
334
+
335
+ } catch (err) {
336
+ console.error('[VAI Excel Fix] exportDeliveryExcel error:', err);
337
+ alert('❌ Lỗi: ' + err.message);
338
+ return false;
339
+ }
340
+ }
341
+
342
+ // ===== CLICK HANDLER =====
343
+ function handleExportClick(e) {
344
+ var btn = e.target.closest('button, a');
345
+ if (!btn) return;
346
+
347
+ var txt = (btn.textContent || '').toLowerCase();
348
+
349
+ // Quote Excel - "Xuất Excel" button in quote modal
350
+ if (txt.includes('excel') && !txt.includes('giao hàng') && !txt.includes('gh-') && !txt.includes('giao')) {
351
+ e.preventDefault();
352
+ e.stopPropagation();
353
+
354
+ if (btn.dataset.vaiExporting === '1') return;
355
+ setBtnState(btn, true);
356
+
357
+ var qd = getQuoteData();
358
+ var code = generateOrderCode(qd.customer?.name || '');
359
+
360
+ exportQuoteExcel(qd, code).then(function(success) {
361
+ setBtnState(btn, false);
362
+ if (success && typeof showToast === 'function') {
363
+ showToast('✅ Excel báo giá tải thành công!');
364
+ }
365
+ });
366
+
367
+ return false;
368
+ }
369
+
370
+ // Delivery Excel - "GH Excel" button in order modal
371
+ if ((txt.includes('gh') || txt.includes('giao hàng')) && txt.includes('excel')) {
372
+ e.preventDefault();
373
+ e.stopPropagation();
374
+
375
+ if (btn.dataset.vaiExporting === '1') return;
376
+ setBtnState(btn, true);
377
+
378
+ // Check for order modal data
379
+ var order = window.VAI_CURRENT_ORDER;
380
+ var exportData, orderCode;
381
+
382
+ if (order && order.items) {
383
+ // From order modal
384
+ exportData = {
385
+ customer: { name: order.customer || '', phone: order.phone || '', addr: order.addr || '', date: order.date || '' },
386
+ items: (order.items || []).map(function(it, i) {
387
+ return {
388
+ stt: i + 1,
389
+ name: it.name || '',
390
+ model: it.model || '',
391
+ qty: it.qty || 1,
392
+ note: it.note || '',
393
+ specs: it.specs || '',
394
+ price: it.price || 0,
395
+ discPrice: it.discPrice || it.price || 0,
396
+ total: it.total || 0
397
+ };
398
+ }),
399
+ grandTotal: order.grandTotal || 0
400
+ };
401
+ orderCode = order.code || generateOrderCode(order.customer || '');
402
+ } else {
403
+ // From cart
404
+ exportData = getQuoteData();
405
+ orderCode = generateOrderCode(exportData.customer?.name || '');
406
+ }
407
+
408
+ exportDeliveryExcel(exportData, orderCode).then(function(success) {
409
+ setBtnState(btn, false);
410
+ if (success && typeof showToast === 'function') {
411
+ showToast('✅ Excel giao hàng tải thành công!');
412
+ }
413
+ });
414
+
415
+ return false;
416
+ }
417
+ }
418
+
419
+ // ===== INIT =====
420
+ function init() {
421
+ // Remove any existing handler to avoid duplicates
422
+ document.removeEventListener('click', handleExportClick, true);
423
+ // Add click handler
424
+ document.addEventListener('click', handleExportClick, true);
425
+ console.log('[VAI Excel Fix v2.0] Loaded - Multi-download enabled');
426
+ }
427
+
428
+ // Run on DOM ready
429
+ if (document.readyState === 'loading') {
430
+ document.addEventListener('DOMContentLoaded', init);
431
+ } else {
432
+ init();
433
+ }
434
+
435
+ // Also run after delays for dynamic content
436
+ setTimeout(init, 2000);
437
+ setTimeout(init, 5000);
438
+
439
+ // Export for global use
440
+ window.VAI_EXCEL_FIX = {
441
+ init: init,
442
+ exportQuoteExcel: exportQuoteExcel,
443
+ exportDeliveryExcel: exportDeliveryExcel,
444
+ generateOrderCode: generateOrderCode,
445
+ getQuoteData: getQuoteData
446
+ };
447
+
448
+ })();
index.html CHANGED
@@ -1,6 +1,6 @@
1
  <!DOCTYPE html>
2
  <html lang="vi">
3
- <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":"HF_TOKEN_REDACTED","REBUILD_TRIGGER":"5"}};</script>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
  <title>V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi</title>
@@ -34,8 +34,8 @@ img{max-width:100%;display:block}a{text-decoration:none;color:inherit}
34
  <script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
35
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
36
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
37
- <!-- V.AI STUDIO Excel Export Fix v1039 - Multi-download + QR Payment -->
38
- <script src="qr-payment-v1039.js?v=5"></script>
39
 
40
  <!-- REST OF THE HTML CONTINUES... -->
41
  <div class="topbar"><div class="container">
 
1
  <!DOCTYPE html>
2
  <html lang="vi">
3
+ <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":"HF_TOKEN_REDACTED","REBUILD_TRIGGER":"6"}};</script>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
  <title>V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi</title>
 
34
  <script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
35
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
36
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
37
+ <!-- V.AI STUDIO Excel Export Fix v2.0 - Multi-download + Blob Registry -->
38
+ <script src="excel-fix-v2.js?v=6"></script>
39
 
40
  <!-- REST OF THE HTML CONTINUES... -->
41
  <div class="topbar"><div class="container">