Cập nhật cache buster cuối cùng v7

#13
by bep40 - opened
Files changed (2) hide show
  1. index.html +3 -3
  2. vai-excel-multi-v2.js +312 -0
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":"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,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 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">
 
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>
 
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">
vai-excel-multi-v2.js ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO - Excel Multi-Download Fix v2
3
+ * FIX TRIỆT ĐỐI CHO SPACE CÓ DẤU CHẤM
4
+ * - Registry toàn cục blob URLs
5
+ * - Patch URL.revokeObjectURL - không revoke khi chưa unload
6
+ * - Multi-download hoạt động 100%
7
+ */
8
+ (function(){
9
+ "use strict";
10
+
11
+ // Global blob registry
12
+ window.__VAI_BLOB_REG = window.__VAI_BLOB_REG || { blobs: [], isUnloading: false };
13
+ var REG = window.__VAI_BLOB_REG;
14
+
15
+ // Cleanup on unload
16
+ function cleanup() {
17
+ REG.isUnloading = true;
18
+ REG.blobs.forEach(function(e) {
19
+ if (!e.revoked && e.url) {
20
+ try { URL.revokeObjectURL(e.url); e.revoked = true; } catch(err) {}
21
+ }
22
+ });
23
+ REG.blobs = [];
24
+ }
25
+ window.addEventListener('pagehide', cleanup);
26
+ window.addEventListener('beforeunload', cleanup);
27
+ window.addEventListener('unload', cleanup);
28
+
29
+ // PATCH URL.revokeObjectURL - QUAN TRỌNG NHẤT
30
+ if (!window.__VAI_REVOKE_DONE) {
31
+ var _origRevoke = URL.revokeObjectURL;
32
+ URL.revokeObjectURL = function(url) {
33
+ if (REG.isUnloading) return _origRevoke.call(this, url);
34
+ if (!url || typeof url !== 'string' || !url.startsWith('blob:')) {
35
+ return _origRevoke.call(this, url);
36
+ }
37
+ // Register nhưng đừng revoke - fix multi-download
38
+ var exists = REG.blobs.find(function(e) { return e.url === url; });
39
+ if (!exists) {
40
+ REG.blobs.push({ url: url, revoked: false, created: Date.now() });
41
+ }
42
+ console.log('[VAI] Blob preserved for multi-download');
43
+ };
44
+ window.__VAI_REVOKE_DONE = true;
45
+ console.log('[VAI] URL.revokeObjectURL PATCHED - Multi-download enabled');
46
+ }
47
+
48
+ // Download function
49
+ function dl(blob, name) {
50
+ var url = URL.createObjectURL(blob);
51
+ REG.blobs.push({ url: url, revoked: false });
52
+ var a = document.createElement('a');
53
+ a.href = url;
54
+ a.download = name;
55
+ a.style.cssText = 'position:absolute;left:-9999px;top:-9999px';
56
+ document.body.appendChild(a);
57
+ a.click();
58
+ setTimeout(function() { try { document.body.removeChild(a); } catch(err) {} }, 100);
59
+ }
60
+
61
+ // Get quote data
62
+ function getQD() {
63
+ var d = { customer: { name: '', phone: '', email: '', addr: '', date: new Date().toLocaleDateString('vi-VN') }, items: [], grandTotal: 0 };
64
+ var cart = window.cart || [];
65
+ var prods = window.D || [];
66
+ cart.forEach(function(item, i) {
67
+ var p = prods[item.idx] || (prods.find ? prods.find(function(x) { return x.id === item.id; }) : null);
68
+ var pr = item.priceNum || item.price || (p ? p.price : 0) || 0;
69
+ d.items.push({
70
+ stt: i + 1,
71
+ image: p ? p.img : '',
72
+ name: p ? (p.name || p.n || '') : (item.name || ''),
73
+ model: p ? (p.sku || p.mod || '') : (item.sku || item.model || ''),
74
+ specs: p ? (p.info || p.desc || '') : '',
75
+ qty: item.qty || 1,
76
+ price: pr,
77
+ discPrice: pr,
78
+ total: (item.qty || 1) * pr,
79
+ note: ''
80
+ });
81
+ });
82
+ var ns = ['#quoteName', '#quoteCustomerName', '#checkoutName'];
83
+ for (var j = 0; j < ns.length; j++) {
84
+ var el = document.querySelector(ns[j]);
85
+ if (el && el.value && el.value.trim()) { d.customer.name = el.value.trim(); break; }
86
+ }
87
+ var ph = document.querySelector('#quotePhone, #checkoutPhone'); if (ph && ph.value) d.customer.phone = ph.value.trim();
88
+ var em = document.querySelector('#quoteEmail'); if (em && em.value) d.customer.email = em.value.trim();
89
+ var ad = document.querySelector('#quoteAddr, #checkoutAddr'); if (ad && ad.value) d.customer.addr = ad.value.trim();
90
+ d.grandTotal = d.items.reduce(function(s, i) { return s + (i.total || 0); }, 0);
91
+ return d;
92
+ }
93
+
94
+ // Format
95
+ function fmt(n) { return n && !isNaN(n) ? Number(n).toLocaleString('vi-VN') + 'đ' : '0đ'; }
96
+
97
+ // Strip diacritics
98
+ function strip(s) { return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[Đđ]/g, 'D'); }
99
+
100
+ // Generate code
101
+ function genCode(name) {
102
+ var d = new Date();
103
+ var dd = String(d.getDate()).padStart(2, '0');
104
+ var mm = String(d.getMonth() + 1).padStart(2, '0');
105
+ var yy = String(d.getFullYear()).slice(-2);
106
+ var ini = '';
107
+ if (name && name.trim()) {
108
+ name.trim().split(/\s+/).forEach(function(w) {
109
+ if (w) {
110
+ var c = strip(w.charAt(0)).toUpperCase();
111
+ if (/[A-Z]/.test(c)) ini += c;
112
+ }
113
+ });
114
+ }
115
+ return 'VAS' + (ini || 'X') + dd + mm + yy;
116
+ }
117
+
118
+ // Button state
119
+ function setBtn(b, exporting) {
120
+ if (exporting) {
121
+ b.disabled = true;
122
+ b.dataset.vaiExporting = '1';
123
+ b.dataset.vaiOrig = b.innerHTML;
124
+ b.innerHTML = '<span style="opacity:0.7">⏳</span>';
125
+ } else {
126
+ b.disabled = false;
127
+ b.dataset.vaiExporting = '0';
128
+ b.dataset.vaiOrig && (b.innerHTML = b.dataset.vaiOrig);
129
+ }
130
+ }
131
+
132
+ // Export Excel
133
+ async function exportExcel() {
134
+ var qd = getQD();
135
+ var code = genCode(qd.customer.name || '');
136
+ if (!qd.items.length || typeof ExcelJS === 'undefined') {
137
+ alert('⚠️ ' + (!qd.items.length ? 'Không có sản phẩm' : 'Chưa có ExcelJS'));
138
+ return;
139
+ }
140
+
141
+ try {
142
+ var wb = new ExcelJS.Workbook();
143
+ var ws = wb.addWorksheet('Báo giá');
144
+ ws.views = [{ showGridLines: false }];
145
+ ws.columns = [{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
146
+
147
+ ws.mergeCells('A3:J3');
148
+ ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
149
+ ws.getCell('A3').font = { bold: true, size: 18, color: { argb: 'FFDB9815' } };
150
+
151
+ ws.getCell('A5').value = 'Khách hàng:';
152
+ ws.mergeCells('B5:E5'); ws.getCell('B5').value = qd.customer.name;
153
+ ws.getCell('H5').value = 'Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value = code;
154
+ ws.getCell('A6').value = 'SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value = qd.customer.phone;
155
+ ws.getCell('H6').value = 'Ngày:'; ws.mergeCells('I6:J6'); ws.getCell('I6').value = qd.customer.date;
156
+ ws.getCell('A7').value = 'Email:'; ws.mergeCells('B7:E7'); ws.getCell('B7').value = qd.customer.email;
157
+ ws.getCell('A8').value = 'Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value = qd.customer.addr;
158
+
159
+ var hr = ws.getRow(10);
160
+ ['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){
161
+ var c = hr.getCell(i+1);
162
+ 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, i) {
169
+ var row = ws.getRow(cr);
170
+ row.height = 50;
171
+ var bg = i % 2 === 0 ? 'FFF8FAFC' : 'FFFFFFFF';
172
+ row.getCell(1).value = it.stt;
173
+ row.getCell(3).value = it.name;
174
+ row.getCell(3).font = { bold: true, size: 9 };
175
+ row.getCell(4).value = it.model;
176
+ row.getCell(5).value = it.specs;
177
+ row.getCell(5).font = { size: 8, color: { argb: 'FF64748B' } };
178
+ row.getCell(6).value = Number(it.qty);
179
+ row.getCell(7).value = Number(it.price);
180
+ row.getCell(7).numFmt = '#,##0"đ"';
181
+ row.getCell(8).value = Number(it.discPrice);
182
+ row.getCell(8).numFmt = '#,##0"đ"';
183
+ row.getCell(9).value = Number(it.total);
184
+ row.getCell(9).numFmt = '#,##0"đ"';
185
+ row.getCell(9).font = { bold: true };
186
+ cr++;
187
+ });
188
+
189
+ ws.getRow(cr).height = 28;
190
+ ws.mergeCells(cr,1,cr,5); ws.getCell('A'+cr).value = 'TỔNG CỘNG';
191
+ ws.getCell('A'+cr).font = { bold: true, color: { argb: 'FFFFFFFF' } };
192
+ ws.getCell('A'+cr).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
193
+ ws.getCell('E'+cr).value = { formula: 'SUM(I'+firstRow+':I'+(cr-1)+')', result: qd.grandTotal };
194
+ ws.getCell('E'+cr).numFmt = '#,##0"đ"';
195
+
196
+ wb.calcProperties.fullCalcOnLoad = true;
197
+
198
+ var buf = await wb.xlsx.writeBuffer();
199
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
200
+ dl(blob, code + '.xlsx');
201
+
202
+ if (typeof showToast === 'function') showToast('✅ Excel báo giá tải thành công!');
203
+ } catch(err) {
204
+ console.error('[VAI] Export error:', err);
205
+ alert('❌ Lỗi: ' + err.message);
206
+ }
207
+ }
208
+
209
+ // Export delivery
210
+ async function exportDelivery() {
211
+ var order = window.VAI_CURRENT_ORDER;
212
+ var code = genCode((order && order.customer) ? order.customer : '');
213
+
214
+ var qd = order && order.items ? { customer: { name: order.customer || '', phone: order.phone || '', addr: order.addr || '', date: '' }, items: (order.items || []).map(function(it, i) {
215
+ return { stt: i+1, name: it.name || '', model: it.model || '', qty: it.qty || 1, note: it.note || '', specs: it.specs || '', price: it.price || 0, discPrice: it.discPrice || it.price || 0, total: it.total || 0 };
216
+ }), grandTotal: order.grandTotal || 0 } : getQD();
217
+
218
+ if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS');
219
+ if (!qd.items.length) return alert('⚠️ Không có sản phẩm');
220
+
221
+ try {
222
+ var wb = new ExcelJS.Workbook();
223
+ var ws = wb.addWorksheet('Giao hàng');
224
+ ws.views = [{ showGridLines: false }];
225
+ ws.columns = [{width:5},{width:34},{width:14},{width:8},{width:20}];
226
+
227
+ ws.mergeCells('A1:E1');
228
+ ws.getCell('A1').value = 'PHIẾU GIAO HÀNG';
229
+ ws.getCell('A1').font = { bold: true, size: 16, color: { argb: 'FF003F62' } };
230
+
231
+ ws.getCell('A2').value = 'KH: ' + qd.customer.name;
232
+ ws.getCell('D2').value = 'Mã: ' + code;
233
+ ws.getCell('A3').value = 'SĐT: ' + qd.customer.phone;
234
+ ws.getCell('A4').value = 'Địa chỉ: ' + qd.customer.addr;
235
+
236
+ var hr = ws.getRow(6);
237
+ ['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h,i){
238
+ var c = hr.getCell(i+1);
239
+ c.value = h;
240
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
241
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
242
+ });
243
+
244
+ var cr = 7, totalQty = 0;
245
+ qd.items.forEach(function(it, i) {
246
+ var row = ws.getRow(cr);
247
+ row.height = 20;
248
+ row.getCell(1).value = it.stt;
249
+ row.getCell(2).value = it.name;
250
+ row.getCell(3).value = it.model;
251
+ row.getCell(4).value = it.qty;
252
+ row.getCell(5).value = it.note;
253
+ totalQty += Number(it.qty);
254
+ cr++;
255
+ });
256
+
257
+ ws.mergeCells(cr,1,cr,3); ws.getCell('A'+cr).value = 'TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D'+cr).value = totalQty;
258
+
259
+ var buf = await wb.xlsx.writeBuffer();
260
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
261
+ dl(blob, 'GH-' + code + '.xlsx');
262
+
263
+ if (typeof showToast === 'function') showToast('✅ Excel giao hàng tải thành công!');
264
+ } catch(err) {
265
+ console.error('[VAI] Delivery export error:', err);
266
+ alert('❌ Lỗi: ' + err.message);
267
+ }
268
+ }
269
+
270
+ // Click handler
271
+ function clickHdl(e) {
272
+ var btn = e.target.closest('button, a');
273
+ if (!btn) return;
274
+ var txt = (btn.textContent || '').toLowerCase();
275
+ var btnAct = document.activeElement;
276
+
277
+ // Quote Excel
278
+ if (txt.includes('excel') && !txt.includes('giao') && !txt.includes('gh-')) {
279
+ e.preventDefault(); e.stopPropagation();
280
+ if (btn.dataset.vaiExporting === '1') return;
281
+ setBtn(btn, true);
282
+ exportExcel().finally(function() { setBtn(btn, false); });
283
+ return false;
284
+ }
285
+
286
+ // Delivery Excel
287
+ if ((txt.includes('giao hàng') || txt.includes('gh')) && txt.includes('excel')) {
288
+ e.preventDefault(); e.stopPropagation();
289
+ if (btn.dataset.vaiExporting === '1') return;
290
+ setBtn(btn, true);
291
+ exportDelivery().finally(function() { setBtn(btn, false); });
292
+ return false;
293
+ }
294
+ }
295
+
296
+ // Init
297
+ function init() {
298
+ document.removeEventListener('click', clickHdl, true);
299
+ document.addEventListener('click', clickHdl, true);
300
+ console.log('[VAI v2] EXCEL MULTI-DOWNLOAD FIX ACTIVE');
301
+ }
302
+
303
+ if (document.readyState === 'loading') {
304
+ document.addEventListener('DOMContentLoaded', init);
305
+ } else {
306
+ init();
307
+ }
308
+ setTimeout(init, 2000);
309
+ setTimeout(init, 5000);
310
+
311
+ window.VAI_EXCEL_V2 = { init, exportExcel, exportDelivery, getQD };
312
+ })();