Inline Excel Multi-Download Fix - tránh cache CDN

#14
by bep40 - opened
Files changed (1) hide show
  1. index.html +243 -6
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":"7"}};</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>
@@ -26,18 +26,255 @@ body{font-family:'Inter',system-ui,sans-serif;color:var(--d);background:var(--l)
26
  ::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--p);border-radius:3px}
27
  img{max-width:100%;display:block}a{text-decoration:none;color:inherit}
28
  .container{max-width:1360px;margin:0 auto;padding:0 20px}
29
- /* ===== V.AI STUDIO CUSTOM CSS ===== */
30
  </style>
31
  </head>
32
  <body>
33
- <!-- Libraries for quotation export - MUST load before custom script -->
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 - TRIỆT ĐỐI CHO SPACE DẤU CHẤM -->
38
- <script src="vai-excel-multi-v2.js?v=20260716"></script>
39
 
40
- <!-- REST OF THE HTML CONTINUES... -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  <div class="topbar"><div class="container">
42
  <div class="topbar-item"><i class="fas fa-truck-fast"></i> Miễn phí vận chuyển</div>
43
  <div class="topbar-item"><i class="fas fa-shield-halved"></i> Bảo hành chính hãng</div>
 
1
  <!DOCTYPE html>
2
  <html lang="vi">
3
+ <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":"HF_TOKEN_REDACTED","REBUILD_TRIGGER":"INLINE"}};</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>
 
26
  ::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--p);border-radius:3px}
27
  img{max-width:100%;display:block}a{text-decoration:none;color:inherit}
28
  .container{max-width:1360px;margin:0 auto;padding:0 20px}
 
29
  </style>
30
  </head>
31
  <body>
32
+ <!-- Libraries for quotation export -->
33
  <script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
34
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
35
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
 
 
36
 
37
+ <!-- V.AI STUDIO Excel Multi-Download Fix (INLINE - tránh cache CDN) -->
38
+ <script>
39
+ (function(){
40
+ "use strict";
41
+
42
+ // Registry toàn cục
43
+ window.__VAI_BLOB_REG = window.__VAI_BLOB_REG || { blobs: [], isUnloading: false };
44
+ var REG = window.__VAI_BLOB_REG;
45
+
46
+ // Cleanup khi unload
47
+ function cleanup() {
48
+ REG.isUnloading = true;
49
+ REG.blobs.forEach(function(e) {
50
+ if (!e.revoked && e.url) { try { URL.revokeObjectURL(e.url); e.revoked = true; } catch(err) {} }
51
+ });
52
+ REG.blobs = [];
53
+ }
54
+ window.addEventListener('pagehide', cleanup);
55
+ window.addEventListener('beforeunload', cleanup);
56
+ window.addEventListener('unload', cleanup);
57
+
58
+ // PATCH URL.revokeObjectURL - KHÔNG REVOKE BLOB
59
+ if (!window.__VAI_REVOKE_PATCHED) {
60
+ var _orig = URL.revokeObjectURL;
61
+ URL.revokeObjectURL = function(url) {
62
+ if (REG.isUnloading) return _orig.call(this, url);
63
+ if (!url || typeof url !== 'string' || !url.startsWith('blob:')) {
64
+ return _orig.call(this, url);
65
+ }
66
+ var exists = REG.blobs.find(function(e) { return e.url === url; });
67
+ if (!exists) REG.blobs.push({ url: url, revoked: false });
68
+ return;
69
+ };
70
+ window.__VAI_REVOKE_PATCHED = true;
71
+ console.log('[VAI] REVOKE PATCHED - Multi-download ENABLED');
72
+ }
73
+
74
+ function dl(blob, name) {
75
+ var url = URL.createObjectURL(blob);
76
+ REG.blobs.push({ url: url, revoked: false });
77
+ var a = document.createElement('a');
78
+ a.href = url;
79
+ a.download = name;
80
+ a.style.cssText = 'position:absolute;left:-9999px';
81
+ document.body.appendChild(a);
82
+ a.click();
83
+ setTimeout(function() { try { document.body.removeChild(a); } catch(err) {} }, 100);
84
+ }
85
+
86
+ function fmt(n) { return n && !isNaN(n) ? Number(n).toLocaleString('vi-VN') + 'đ' : '0đ'; }
87
+
88
+ function strip(s) { return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[Đđ]/g, 'D'); }
89
+
90
+ function genCode(name) {
91
+ var d = new Date(), dd = String(d.getDate()).padStart(2, '0');
92
+ var mm = String(d.getMonth() + 1).padStart(2, '0');
93
+ var yy = String(d.getFullYear()).slice(-2), ini = '';
94
+ if (name && name.trim()) {
95
+ name.trim().split(/\s+/).forEach(function(w) {
96
+ if (w) {
97
+ var c = strip(w.charAt(0)).toUpperCase();
98
+ if (/[A-Z]/.test(c)) ini += c;
99
+ }
100
+ });
101
+ }
102
+ return 'VAS' + (ini || 'X') + dd + mm + yy;
103
+ }
104
+
105
+ function setBtn(b, exporting) {
106
+ if (exporting) {
107
+ b.disabled = true;
108
+ b.dataset.vaiExporting = '1';
109
+ b.dataset.vaiOrig = b.innerHTML;
110
+ b.innerHTML = '<span style="opacity:0.7">⏳</span>';
111
+ } else {
112
+ b.disabled = false;
113
+ b.dataset.vaiExporting = '0';
114
+ b.dataset.vaiOrig && (b.innerHTML = b.dataset.vaiOrig);
115
+ }
116
+ }
117
+
118
+ function getQD() {
119
+ var d = { customer: { name: '', phone: '', email: '', addr: '', date: new Date().toLocaleDateString('vi-VN') }, items: [], grandTotal: 0 };
120
+ var cart = window.cart || [], prods = window.D || [];
121
+ cart.forEach(function(item, i) {
122
+ var p = prods[item.idx] || (prods.find ? prods.find(function(x) { return x.id === item.id; }) : null);
123
+ var pr = item.priceNum || item.price || (p ? p.price : 0) || 0;
124
+ d.items.push({
125
+ stt: i + 1, name: p ? (p.name || p.n || '') : (item.name || ''),
126
+ model: p ? (p.sku || p.mod || '') : (item.sku || item.model || ''),
127
+ qty: item.qty || 1, price: pr, discPrice: pr, total: (item.qty || 1) * pr
128
+ });
129
+ });
130
+ ['#quoteName', '#quoteCustomerName', '#checkoutName'].forEach(function(sel) {
131
+ var el = document.querySelector(sel);
132
+ if (el && el.value && el.value.trim()) { d.customer.name = el.value.trim(); return; }
133
+ });
134
+ var ph = document.querySelector('#quotePhone, #checkoutPhone'); if (ph && ph.value) d.customer.phone = ph.value.trim();
135
+ var em = document.querySelector('#quoteEmail'); if (em && em.value) d.customer.email = em.value.trim();
136
+ var ad = document.querySelector('#quoteAddr, #checkoutAddr'); if (ad && ad.value) d.customer.addr = ad.value.trim();
137
+ d.grandTotal = d.items.reduce(function(s, i) { return s + (i.total || 0); }, 0);
138
+ return d;
139
+ }
140
+
141
+ async function exportExcel() {
142
+ var qd = getQD(), code = genCode(qd.customer.name || '');
143
+ if (!qd.items.length || typeof ExcelJS === 'undefined') {
144
+ alert(!qd.items.length ? '⚠️ Không có sản phẩm' : '⚠️ Chưa có ExcelJS');
145
+ return;
146
+ }
147
+ try {
148
+ var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Báo giá');
149
+ ws.views = [{ showGridLines: false }];
150
+ ws.columns = [{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
151
+ ws.mergeCells('A3:J3'); ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
152
+ ws.getCell('A3').font = { bold: true, size: 18, color: { argb: 'FFDB9815' } };
153
+ ws.getCell('A5').value = 'Khách hàng:'; ws.mergeCells('B5:E5'); ws.getCell('B5').value = qd.customer.name;
154
+ ws.getCell('H5').value = 'Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value = code;
155
+ ws.getCell('A6').value = 'SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value = qd.customer.phone;
156
+ ws.getCell('H6').value = 'Ngày:'; ws.mergeCells('I6:J6'); ws.getCell('I6').value = qd.customer.date;
157
+ ws.getCell('A7').value = 'Email:'; ws.mergeCells('B7:E7'); ws.getCell('B7').value = qd.customer.email;
158
+ ws.getCell('A8').value = 'Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value = qd.customer.addr;
159
+
160
+ var hr = ws.getRow(10);
161
+ ['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) {
162
+ var c = hr.getCell(i+1); c.value = h;
163
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
164
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
165
+ });
166
+
167
+ var cr = 11, firstRow = cr;
168
+ qd.items.forEach(function(it) {
169
+ var row = ws.getRow(cr); row.height = 50;
170
+ var bg = (cr - 11) % 2 === 0 ? 'FFF8FAFC' : 'FFFFFFFF';
171
+ row.getCell(1).value = it.stt;
172
+ row.getCell(3).value = it.name; row.getCell(3).font = { bold: true };
173
+ row.getCell(4).value = it.model;
174
+ row.getCell(5).value = it.specs;
175
+ row.getCell(6).value = Number(it.qty); row.getCell(6).numFmt = '#,##0';
176
+ row.getCell(7).value = Number(it.price); row.getCell(7).numFmt = '#,##0"đ"';
177
+ row.getCell(8).value = Number(it.discPrice); row.getCell(8).numFmt = '#,##0"đ"';
178
+ row.getCell(9).value = Number(it.total); row.getCell(9).numFmt = '#,##0"đ"';
179
+ cr++;
180
+ });
181
+
182
+ ws.getRow(cr).height = 28; ws.mergeCells(cr,1,cr,5); ws.getCell('A'+cr).value = 'TỔNG CỘNG';
183
+ ws.getCell('A'+cr).font = { bold: true, color: { argb: 'FFFFFFFF' } };
184
+ ws.getCell('A'+cr).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
185
+ ws.getCell('E'+cr).value = { formula: 'SUM(I'+firstRow+':I'+(cr-1)+')', result: qd.grandTotal };
186
+ ws.getCell('E'+cr).numFmt = '#,##0"đ"'; wb.calcProperties.fullCalcOnLoad = true;
187
+
188
+ var buf = await wb.xlsx.writeBuffer();
189
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
190
+ dl(blob, code + '.xlsx');
191
+ if (typeof showToast === 'function') showToast('✅ Excel tải thành công!');
192
+ } catch(err) {
193
+ alert('❌ Lỗi: ' + err.message);
194
+ }
195
+ }
196
+
197
+ async function exportDelivery() {
198
+ var order = window.VAI_CURRENT_ORDER;
199
+ var qd = order && order.items ? { customer: { name: order.customer || '', phone: order.phone || '', addr: order.addr || '' }, items: order.items.map(function(it, i) {
200
+ return { stt: i+1, name: it.name || '', model: it.model || '', qty: it.qty || 1, note: it.note || '', price: it.price || 0, discPrice: it.discPrice || it.price || 0 };
201
+ }) } : getQD();
202
+ var code = genCode(qd.customer.name || '');
203
+
204
+ if (typeof ExcelJS === 'undefined' || !qd.items.length) return;
205
+ try {
206
+ var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Giao hàng');
207
+ ws.views = [{ showGridLines: false }];
208
+ ws.columns = [{width:5},{width:34},{width:14},{width:8},{width:20}];
209
+ ws.mergeCells('A1:E1'); ws.getCell('A1').value = 'PHIẾU GIAO HÀNG';
210
+ ws.getCell('A1').font = { bold: true, size: 16 };
211
+ ws.getCell('A2').value = 'KH: ' + qd.customer.name;
212
+ ws.getCell('D2').value = 'Mã: ' + code;
213
+
214
+ var hr = ws.getRow(6);
215
+ ['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h, i) {
216
+ var c = hr.getCell(i+1); c.value = h;
217
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
218
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
219
+ });
220
+
221
+ var cr = 7, totalQty = 0;
222
+ qd.items.forEach(function(it) {
223
+ var row = ws.getRow(cr);
224
+ row.getCell(1).value = it.stt;
225
+ row.getCell(2).value = it.name;
226
+ row.getCell(3).value = it.model;
227
+ row.getCell(4).value = it.qty;
228
+ row.getCell(5).value = it.note;
229
+ totalQty += Number(it.qty);
230
+ cr++;
231
+ });
232
+ ws.mergeCells(cr,1,cr,3); ws.getCell('A'+cr).value = 'TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D'+cr).value = totalQty;
233
+
234
+ var buf = await wb.xlsx.writeBuffer();
235
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
236
+ dl(blob, 'GH-' + code + '.xlsx');
237
+ if (typeof showToast === 'function') showToast('✅ Excel giao hàng tải thành công!');
238
+ } catch(err) {}
239
+ }
240
+
241
+ function clickHdl(e) {
242
+ var btn = e.target.closest('button, a');
243
+ if (!btn) return;
244
+ var txt = (btn.textContent || '').toLowerCase();
245
+
246
+ if (txt.includes('excel') && !txt.includes('giao') && !txt.includes('gh-')) {
247
+ e.preventDefault(); e.stopPropagation();
248
+ if (btn.dataset.vaiExporting === '1') return;
249
+ setBtn(btn, true);
250
+ exportExcel().finally(function() { setBtn(btn, false); });
251
+ return false;
252
+ }
253
+ if ((txt.includes('giao hàng') || txt.includes('gh')) && txt.includes('excel')) {
254
+ e.preventDefault(); e.stopPropagation();
255
+ if (btn.dataset.vaiExporting === '1') return;
256
+ setBtn(btn, true);
257
+ exportDelivery().finally(function() { setBtn(btn, false); });
258
+ return false;
259
+ }
260
+ }
261
+
262
+ function init() {
263
+ document.removeEventListener('click', clickHdl, true);
264
+ document.addEventListener('click', clickHdl, true);
265
+ console.log('[VAI INLINE v2] EXCEL MULTI-DOWNLOAD ACTIVE');
266
+ }
267
+
268
+ if (document.readyState === 'loading') {
269
+ document.addEventListener('DOMContentLoaded', init);
270
+ } else { init(); }
271
+ setTimeout(init, 2000);
272
+ setTimeout(init, 5000);
273
+
274
+ window.VAI_EXCEL = { init, exportExcel, exportDelivery };
275
+ })();
276
+ </script>
277
+
278
  <div class="topbar"><div class="container">
279
  <div class="topbar-item"><i class="fas fa-truck-fast"></i> Miễn phí vận chuyển</div>
280
  <div class="topbar-item"><i class="fas fa-shield-halved"></i> Bảo hành chính hãng</div>