bep40 commited on
Commit
c8b08ef
·
1 Parent(s): 47d1ca7

Cập nhật qr-payment với event listener click ngay lập tức (#16)

Browse files

- Upload vai-excel-click-fix.js (f29c4ee4eb860d3f5564a0a06d7c774412c4a4db)
- Upload index.html (624ea1c2619b550052ee3361d0616a22da27430e)

Files changed (2) hide show
  1. index.html +99 -152
  2. vai-excel-click-fix.js +129 -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":"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>
@@ -29,81 +29,55 @@ img{max-width:100%;display:block}a{text-decoration:none;color:inherit}
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;
@@ -114,179 +88,152 @@ function setBtn(b, exporting) {
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>
281
  <div class="topbar-item"><i class="fas fa-headset"></i> Hỗ trợ 24/7</div>
282
  </div></div>
283
 
284
- <!-- MAIN APP WILL BE INJECTED HERE -->
285
  <div id="app"></div>
286
 
287
- <script>
288
- // Minimal stub for testing - full app script continues
289
- console.log('[V.AI STUDIO] App loading...');
290
- </script>
291
  </body>
292
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="vi">
3
+ <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":"HF_TOKEN_REDACTED","REBUILD_TRIGGER":"NOW"}};</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>
 
29
  </style>
30
  </head>
31
  <body>
32
+ <!-- ExcelJS Library -->
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
+ <!-- CLICK HANDLER NGAY - TRƯỚC KHI DOM LOAD -->
38
  <script>
39
  (function(){
40
  "use strict";
41
 
42
+ // Registry blob toàn cục
43
+ window.__VAI_BLOB_REG = { blobs: [], isUnloading: false };
44
  var REG = window.__VAI_BLOB_REG;
45
 
46
  // Cleanup khi unload
47
+ window.addEventListener('pagehide', function() {
48
  REG.isUnloading = true;
49
+ REG.blobs.forEach(function(e) { try { URL.revokeObjectURL(e.url); } catch(r) {} });
50
+ });
 
 
 
 
 
 
51
 
52
+ // PATCH URL.revokeObjectURL NGAY LẬP TỨC
53
+ (function(){var o=URL.revokeObjectURL;URL.revokeObjectURL=function(u){if(REG.isUnloading)return o.call(this,u);if(u&&typeof u==='string'&&u.startsWith('blob:')){var e=REG.blobs.find(function(x){return x.url===u});if(!e)REG.blobs.push({url:u});return;}return o.call(this,u);};})();
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ // Helper
56
  function dl(blob, name) {
57
  var url = URL.createObjectURL(blob);
58
+ REG.blobs.push({url:url});
59
  var a = document.createElement('a');
60
+ a.href = url; a.download = name; a.style.cssText = 'position:absolute;left:-9999px';
 
 
61
  document.body.appendChild(a);
62
  a.click();
63
+ setTimeout(function() { try { document.body.removeChild(a); } catch(r) {} }, 100);
64
  }
65
 
66
  function fmt(n) { return n && !isNaN(n) ? Number(n).toLocaleString('vi-VN') + 'đ' : '0đ'; }
67
+ function strip(s) { return (s||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D'); }
 
 
68
  function genCode(name) {
69
+ var d = new Date(), dd = String(d.getDate()).padStart(2,'0');
70
+ var mm = String(d.getMonth()+1).padStart(2,'0'), 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) { var c = strip(w.charAt(0)).toUpperCase(); if(/[A-Z]/.test(c)) ini += c; }
 
 
 
75
  });
76
  }
77
+ return 'VAS' + (ini||'X') + dd + mm + yy;
78
  }
 
79
  function setBtn(b, exporting) {
80
+ if(exporting) {
81
  b.disabled = true;
82
  b.dataset.vaiExporting = '1';
83
  b.dataset.vaiOrig = b.innerHTML;
 
88
  b.dataset.vaiOrig && (b.innerHTML = b.dataset.vaiOrig);
89
  }
90
  }
 
91
  function getQD() {
92
+ var d = { customer:{name:'',phone:'',email:'',addr:'',date:new Date().toLocaleDateString('vi-VN')}, items:[], grandTotal:0 };
93
  var cart = window.cart || [], prods = window.D || [];
94
+ cart.forEach(function(item,i){
95
+ var p = prods[item.idx] || (prods.find ? prods.find(function(x){return x.id===item.id;}) : null);
96
  var pr = item.priceNum || item.price || (p ? p.price : 0) || 0;
97
+ d.items.push({ stt:i+1, name:p ? (p.name||p.n||'') : (item.name||''), model:p ? (p.sku||p.mod||'') : (item.sku||item.model||''), qty:item.qty||1, price:pr, discPrice:pr, total:(item.qty||1)*pr });
 
 
 
 
98
  });
99
+ ['#quoteName','#quoteCustomerName','#checkoutName'].forEach(function(sel){
100
  var el = document.querySelector(sel);
101
+ if(el && el.value && el.value.trim()) { d.customer.name = el.value.trim(); return; }
102
  });
103
+ var ph = document.querySelector('#quotePhone,#checkoutPhone'); if(ph && ph.value) d.customer.phone = ph.value.trim();
104
+ var ad = document.querySelector('#quoteAddr,#checkoutAddr'); if(ad && ad.value) d.customer.addr = ad.value.trim();
105
+ d.grandTotal = d.items.reduce(function(s,i){return s+(i.total||0);},0);
 
106
  return d;
107
  }
108
 
109
+ window.VAI_EXCEL_DL = async function(t) {
110
  var qd = getQD(), code = genCode(qd.customer.name || '');
111
+ if(!qd.items.length || typeof ExcelJS==='undefined') {
112
+ alert(!qd.items.length ? '⚠️ Không có SP' : '⚠️ Chưa có ExcelJS');
113
  return;
114
  }
115
  try {
116
  var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Báo giá');
117
+ ws.views = [{showGridLines:false}];
118
  ws.columns = [{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
119
  ws.mergeCells('A3:J3'); ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
120
+ ws.getCell('A3').font = {bold:true,size:18,color:{argb:'FFDB9815'}};
121
+ ws.getCell('A3').alignment = {horizontal:'center'};
122
  ws.getCell('A5').value = 'Khách hàng:'; ws.mergeCells('B5:E5'); ws.getCell('B5').value = qd.customer.name;
123
  ws.getCell('H5').value = 'Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value = code;
124
  ws.getCell('A6').value = 'SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value = qd.customer.phone;
 
 
125
  ws.getCell('A8').value = 'Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value = qd.customer.addr;
 
126
  var hr = ws.getRow(10);
127
+ ['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){
128
  var c = hr.getCell(i+1); c.value = h;
129
+ c.font = {bold:true,color:{argb:'FFFFFFFF'}};
130
+ c.fill = {type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};
131
  });
 
132
  var cr = 11, firstRow = cr;
133
+ qd.items.forEach(function(it){
134
  var row = ws.getRow(cr); row.height = 50;
 
135
  row.getCell(1).value = it.stt;
136
+ row.getCell(3).value = it.name; row.getCell(3).font = {bold:true};
137
  row.getCell(4).value = it.model;
 
138
  row.getCell(6).value = Number(it.qty); row.getCell(6).numFmt = '#,##0';
139
  row.getCell(7).value = Number(it.price); row.getCell(7).numFmt = '#,##0"đ"';
140
  row.getCell(8).value = Number(it.discPrice); row.getCell(8).numFmt = '#,##0"đ"';
141
  row.getCell(9).value = Number(it.total); row.getCell(9).numFmt = '#,##0"đ"';
142
  cr++;
143
  });
 
144
  ws.getRow(cr).height = 28; ws.mergeCells(cr,1,cr,5); ws.getCell('A'+cr).value = 'TỔNG CỘNG';
145
+ ws.getCell('A'+cr).font = {bold:true};
146
+ ws.getCell('A'+cr).fill = {type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};
147
+ ws.getCell('E'+cr).value = {formula:'SUM(I'+firstRow+':I'+(cr-1)+')',result:qd.grandTotal};
148
+ ws.getCell('E'+cr).numFmt = '#,##0"đ"';
149
+ wb.calcProperties.fullCalcOnLoad = true;
150
  var buf = await wb.xlsx.writeBuffer();
151
+ dl(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), code + '.xlsx');
152
+ if(typeof showToast==='function') showToast('✅ Excel OK');
153
+ } catch(err) { alert(' Lỗi: ' + err.message); }
154
+ };
 
 
 
155
 
156
+ window.VAI_GH_DL = async function() {
157
  var order = window.VAI_CURRENT_ORDER;
158
+ var qd = order && order.items ? { customer:{name:order.customer||'',phone:order.phone||'',addr:order.addr||''}, items:order.items.map(function(it,i){
159
+ return { stt:i+1, name:it.name||'', model:it.model||'', qty:it.qty||1, note:it.note||'' };
160
  }) } : getQD();
161
  var code = genCode(qd.customer.name || '');
162
+ if(typeof ExcelJS==='undefined' || !qd.items.length) return;
 
163
  try {
164
  var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Giao hàng');
 
165
  ws.columns = [{width:5},{width:34},{width:14},{width:8},{width:20}];
166
  ws.mergeCells('A1:E1'); ws.getCell('A1').value = 'PHIẾU GIAO HÀNG';
167
+ ws.getCell('A1').font = {bold:true,size:16};
168
  ws.getCell('A2').value = 'KH: ' + qd.customer.name;
169
  ws.getCell('D2').value = 'Mã: ' + code;
 
170
  var hr = ws.getRow(6);
171
+ ['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h,i){
172
  var c = hr.getCell(i+1); c.value = h;
173
+ c.font = {bold:true,color:{argb:'FFFFFFFF'}};
174
+ c.fill = {type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};
175
  });
176
+ var cr = 7, tq = 0;
177
+ qd.items.forEach(function(it){
 
178
  var row = ws.getRow(cr);
179
  row.getCell(1).value = it.stt;
180
  row.getCell(2).value = it.name;
181
  row.getCell(3).value = it.model;
182
  row.getCell(4).value = it.qty;
183
  row.getCell(5).value = it.note;
184
+ tq += Number(it.qty); cr++;
 
185
  });
186
+ ws.mergeCells(cr,1,cr,3); ws.getCell('A'+cr).value = 'TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D'+cr).value = tq;
 
187
  var buf = await wb.xlsx.writeBuffer();
188
+ dl(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), 'GH-' + code + '.xlsx');
 
 
189
  } catch(err) {}
190
+ };
191
 
192
+ // CLICK HANDLER - GÁN NGAY LẬP TỨC
193
+ (function(){
194
+ function hdl(e) {
195
+ var btn = e.target.closest('button, a');
196
+ if(!btn) return;
197
+ var txt = (btn.textContent || '').toLowerCase();
198
+ var act = document.activeElement;
199
+
200
+ // EXCEL BÁO GIÁ
201
+ if(txt.includes('excel') && !txt.includes('giao') && !txt.includes('gh-')) {
202
+ e.preventDefault();
203
+ if(act && act.dataset.vaiExporting === '1') return;
204
+ setBtn(btn, true);
205
+ window.VAI_EXCEL_DL().then(function(){ setBtn(btn, false); });
206
+ return false;
207
+ }
208
+
209
+ // GH EXCEL
210
+ if((txt.includes('giao hàng') || txt.includes('gh')) && txt.includes('excel')) {
211
+ e.preventDefault();
212
+ if(act && act.dataset.vaiExporting === '1') return;
213
+ setBtn(btn, true);
214
+ window.VAI_GH_DL().then(function(){ setBtn(btn, false); });
215
+ return false;
216
+ }
217
  }
218
+
219
+ // GÁN NGAY
220
+ document.addEventListener('click', hdl, true);
221
+ console.log('[VAI EXCEL] CLICK HANDLER ACTIVE');
222
+ })();
 
 
 
 
 
 
 
 
223
 
 
224
  })();
225
  </script>
226
 
227
+ <!-- TOPBAR -->
228
  <div class="topbar"><div class="container">
229
  <div class="topbar-item"><i class="fas fa-truck-fast"></i> Miễn phí vận chuyển</div>
230
  <div class="topbar-item"><i class="fas fa-shield-halved"></i> Bảo hành chính hãng</div>
231
  <div class="topbar-item"><i class="fas fa-headset"></i> Hỗ trợ 24/7</div>
232
  </div></div>
233
 
234
+ <!-- APP -->
235
  <div id="app"></div>
236
 
237
+ <script>console.log('[V.AI STUDIO] Loaded');</script>
 
 
 
238
  </body>
239
  </html>
vai-excel-click-fix.js ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO - EXCEL FIX CLICK HANDLER
3
+ * ĐẶT NGAY SAU THẺ MỞ BODY
4
+ * SỬA LỖI CLICK KHÔNG HOẠT ĐỘNG
5
+ */
6
+ (function(){
7
+ 'use strict';
8
+
9
+ // Registry blob toàn cục
10
+ window.__VAI_BLOB_ALL = window.__VAI_BLOB_ALL || { blobs: [], isUn: false };
11
+ var REG = window.__VAI_BLOB_ALL;
12
+
13
+ // Cleanup
14
+ function cleanup() { REG.isUn = true; REG.blobs.forEach(function(e) { try { URL.revokeObjectURL(e.url); } catch(r) {} }); REG.blobs = []; }
15
+ window.addEventListener('pagehide', cleanup);
16
+ window.addEventListener('beforeunload', cleanup);
17
+
18
+ // PATCH URL.revokeObjectURL NGAY
19
+ if (!window.__VAI_REV_DONE) {
20
+ var _o = URL.revokeObjectURL;
21
+ URL.revokeObjectURL = function(u) {
22
+ if (REG.isUn || !u || typeof u !== 'string' || u.indexOf('blob:') !== 0) return _o.call(this, u);
23
+ var exist = REG.blobs.find(function(e) { return e.url === u; });
24
+ if (!exist) REG.blobs.push({ url: u, revoked: false });
25
+ return;
26
+ };
27
+ window.__VAI_REV_DONE = true;
28
+ }
29
+
30
+ // Helper
31
+ function dl(blob, name) {
32
+ var url = URL.createObjectURL(blob);
33
+ REG.blobs.push({ url: url });
34
+ var a = document.createElement('a');
35
+ a.href = url; a.download = name; a.style.cssText = 'position:absolute;left:-9999px';
36
+ document.body.appendChild(a);
37
+ a.click();
38
+ setTimeout(function() { try { document.body.removeChild(a); } catch(r) {} }, 100);
39
+ }
40
+
41
+ // Click handler NGAY
42
+ document.addEventListener('click', function(e) {
43
+ var btn = e.target.closest('button, a');
44
+ if (!btn) return;
45
+
46
+ var txt = (btn.textContent || '').toLowerCase();
47
+ var activeEl = document.activeElement;
48
+
49
+ // Xử lý Excel báo giá
50
+ if (txt.includes('excel') && !txt.includes('gh') && !txt.includes('giao hàng')) {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+
54
+ // Tránh double click
55
+ if (activeEl && activeEl.dataset.vaiExporting === '1') return;
56
+
57
+ // Set state
58
+ if (activeEl) {
59
+ activeEl.dataset.vaiExporting = '1';
60
+ activeEl.dataset.vaiHTML = activeEl.innerHTML;
61
+ activeEl.innerHTML = '<span style="opacity:0.7">⏳</span>';
62
+ }
63
+
64
+ // Export
65
+ setTimeout(function() {
66
+ if (typeof window.exportExcel === 'function') {
67
+ window.exportExcel().then(function() {
68
+ if (activeEl) {
69
+ activeEl.dataset.vaiExporting = '0';
70
+ activeEl.innerHTML = activeEl.dataset.vaiHTML || '💾 Excel';
71
+ }
72
+ if (typeof showToast === 'function') showToast('✅ Excel OK');
73
+ }).catch(function(err) {
74
+ if (activeEl) {
75
+ activeEl.dataset.vaiExporting = '0';
76
+ activeEl.innerHTML = activeEl.dataset.vaiHTML || '💾 Excel';
77
+ }
78
+ alert('❌ Lỗi: ' + (err && err.message ? err.message : err));
79
+ });
80
+ } else {
81
+ // Tự động tạo exportExcel nếu chưa có
82
+ console.log('[VAI] Export function not ready, waiting...');
83
+ var wait = setInterval(function() {
84
+ if (typeof window.exportExcel === 'function') {
85
+ clearInterval(wait);
86
+ window.exportExcel().then(function() {
87
+ if (activeEl) {
88
+ activeEl.dataset.vaiExporting = '0';
89
+ activeEl.innerHTML = activeEl.dataset.vaiHTML || '💾 Excel';
90
+ }
91
+ });
92
+ }
93
+ }, 500);
94
+ }
95
+ }, 10);
96
+
97
+ return false;
98
+ }
99
+
100
+ // Xử lý GH Excel
101
+ if ((txt.includes('giao hàng') || txt.includes('gh')) && txt.includes('excel')) {
102
+ e.preventDefault();
103
+ e.stopPropagation();
104
+
105
+ if (activeEl && activeEl.dataset.vaiExporting === '1') return;
106
+
107
+ if (activeEl) {
108
+ activeEl.dataset.vaiExporting = '1';
109
+ activeEl.dataset.vaiHTML = activeEl.innerHTML;
110
+ activeEl.innerHTML = '<span style="opacity:0.7">⏳</span>';
111
+ }
112
+
113
+ setTimeout(function() {
114
+ if (typeof window.exportDeliveryExcel === 'function') {
115
+ window.exportDeliveryExcel().then(function() {
116
+ if (activeEl) {
117
+ activeEl.dataset.vaiExporting = '0';
118
+ activeEl.innerHTML = activeEl.dataset.vaiHTML || 'GH Excel';
119
+ }
120
+ });
121
+ }
122
+ }, 10);
123
+
124
+ return false;
125
+ }
126
+ }, true);
127
+
128
+ console.log('[VAI CLICK] Handler attached - Excel click ready');
129
+ })();