bep40 commited on
Commit
90a4412
·
verified ·
1 Parent(s): c1a8793

Upload auto-inject-excel-fix.js

Browse files
Files changed (1) hide show
  1. auto-inject-excel-fix.js +265 -0
auto-inject-excel-fix.js ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO - Auto Inject Excel Fix
3
+ * Script này tự động chạy khi trang load
4
+ * KHÔNG CẦN BOOKMARKLET - TỰ ĐỘNG HOẠT ĐỘNG
5
+ */
6
+ (function(){
7
+ 'use strict';
8
+
9
+ // ===== AUTO INJECT CONFIG =====
10
+ var AUTO_FIX_VERSION = 'v2.0';
11
+ var SCRIPT_INJECTED = false;
12
+
13
+ // ===== GLOBAL BLOB REGISTRY =====
14
+ window.__VAI_BLOB = window.__VAI_BLOB || { blobs: [], isUnloading: false };
15
+ var REG = window.__VAI_BLOB;
16
+
17
+ // ===== CLEANUP ON UNLOAD =====
18
+ function cleanupBlobs() {
19
+ REG.isUnloading = true;
20
+ REG.blobs.forEach(function(e) {
21
+ e.revoked || (URL.revokeObjectURL(e.url), e.revoked = true);
22
+ });
23
+ REG.blobs = [];
24
+ }
25
+ window.addEventListener('pagehide', cleanupBlobs);
26
+ window.addEventListener('beforeunload', cleanupBlobs);
27
+ window.addEventListener('unload', cleanupBlobs);
28
+
29
+ // ===== PATCH URL.revokeObjectURL - CRITICAL FIX =====
30
+ if (!window.__VAI_REVOKE_PATCHED) {
31
+ var _orig = URL.revokeObjectURL;
32
+ URL.revokeObjectURL = function(url) {
33
+ if (REG.isUnloading || !url || typeof url !== 'string' || !url.startsWith('blob:')) {
34
+ return _orig.call(this, url);
35
+ }
36
+ // Register but DON'T revoke - fix for multi-download
37
+ var exists = REG.blobs.find(function(e) { return e.url === url; });
38
+ exists || REG.blobs.push({ url: url, revoked: false });
39
+ console.log('[Auto Inject] Blob preserved for multi-download');
40
+ return undefined;
41
+ };
42
+ window.__VAI_REVOKE_PATCHED = true;
43
+ console.log('[Auto Inject v2.0] URL.revokeObjectURL patched');
44
+ }
45
+
46
+ // ===== UTILITIES =====
47
+ function fmt(n) { return n && !isNaN(n) ? Number(n).toLocaleString('vi-VN') + 'đ' : '0đ'; }
48
+ function stripDiacritics(s) { return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[Đđ]/g, 'D'); }
49
+ function genOrderCode(name) {
50
+ var d = new Date();
51
+ var dd = String(d.getDate()).padStart(2, '0');
52
+ var mm = String(d.getMonth() + 1).padStart(2, '0');
53
+ var yy = String(d.getFullYear()).slice(-2);
54
+ var ini = '';
55
+ if (name && name.trim()) {
56
+ name.trim().split(/\s+/).forEach(function(w) {
57
+ w && /[A-Z]/.test(stripDiacritics(w.charAt(0))) && (ini += stripDiacritics(w.charAt(0)).toUpperCase());
58
+ });
59
+ }
60
+ return 'VAS' + (ini || 'X') + dd + mm + yy;
61
+ }
62
+
63
+ // ===== BUTTON STATE =====
64
+ function setBtnState(btn, exporting) {
65
+ if (!btn) return;
66
+ if (exporting) {
67
+ btn.disabled = true;
68
+ btn.dataset.vaiExporting = '1';
69
+ btn._orig = btn.innerHTML;
70
+ btn.innerHTML = '<span style="opacity:0.7">⏳</span>';
71
+ } else {
72
+ btn.disabled = false;
73
+ btn.dataset.vaiExporting = '0';
74
+ btn._orig && (btn.innerHTML = btn._orig);
75
+ }
76
+ }
77
+
78
+ // ===== DOWNLOAD BLOB =====
79
+ function downloadBlob(blob, filename) {
80
+ var url = URL.createObjectURL(blob);
81
+ REG.blobs.push({ url: url, revoked: false });
82
+ var a = document.createElement('a');
83
+ a.href = url;
84
+ a.download = filename;
85
+ a.style.cssText = 'position:absolute;left:-9999px;top:-9999px';
86
+ document.body.appendChild(a);
87
+ a.click();
88
+ setTimeout(function() { try { document.body.removeChild(a); } catch(e) {} }, 100);
89
+ }
90
+
91
+ // ===== GET QUOTE DATA =====
92
+ function getQuoteData() {
93
+ var data = {
94
+ customer: { name: '', phone: '', email: '', addr: '', date: new Date().toLocaleDateString('vi-VN') },
95
+ items: [],
96
+ grandTotal: 0
97
+ };
98
+ var cart = window.cart || [];
99
+ var products = window.D || [];
100
+ cart.forEach(function(item, i) {
101
+ var prod = products[item.idx] || (products.find ? products.find(function(p) { return p.id === item.id; }) : null);
102
+ var price = item.priceNum || item.price || (prod ? prod.price : 0) || 0;
103
+ data.items.push({
104
+ stt: i + 1,
105
+ image: prod ? prod.img : '',
106
+ name: prod ? (prod.name || prod.n || '') : (item.name || ''),
107
+ model: prod ? (prod.sku || prod.mod || '') : (item.sku || item.model || ''),
108
+ specs: prod ? (prod.info || prod.desc || '') : '',
109
+ qty: item.qty || 1,
110
+ price: price,
111
+ discPrice: price,
112
+ total: (item.qty || 1) * price,
113
+ note: ''
114
+ });
115
+ });
116
+ // Get customer info
117
+ var nameInputs = ['#quoteName', '#quoteCustomerName', '#checkoutName'];
118
+ for (var j = 0; j < nameInputs.length; j++) {
119
+ var el = document.querySelector(nameInputs[j]);
120
+ if (el && el.value && el.value.trim()) { data.customer.name = el.value.trim(); break; }
121
+ }
122
+ var ph = document.querySelector('#quotePhone, #checkoutPhone'); ph && ph.value && (data.customer.phone = ph.value.trim());
123
+ var em = document.querySelector('#quoteEmail'); em && em.value && (data.customer.email = em.value.trim());
124
+ var ad = document.querySelector('#quoteAddr, #checkoutAddr'); ad && ad.value && (data.customer.addr = ad.value.trim());
125
+ data.grandTotal = data.items.reduce(function(s, i) { return s + (i.total || 0); }, 0);
126
+ return data;
127
+ }
128
+
129
+ // ===== EXPORT FUNCTIONS =====
130
+ async function exportQuoteExcel(quoteData, code) {
131
+ if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS'), false;
132
+ try {
133
+ var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Báo giá');
134
+ ws.views = [{ showGridLines: false }];
135
+ ws.columns = [{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
136
+ ws.mergeCells('A3:J3'); ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
137
+ ws.getCell('A3').font = { bold:true, size:18, color:{argb:'FFDB9815'} }; ws.getCell('A3').alignment = { horizontal:'center' };
138
+ ws.getCell('A5').value = 'Khách hàng:'; ws.mergeCells('B5:E5'); ws.getCell('B5').value = quoteData.customer.name;
139
+ ws.getCell('H5').value = 'Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value = code;
140
+ ws.getCell('A6').value = 'SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value = quoteData.customer.phone;
141
+ ws.getCell('H6').value = 'Ngày:'; ws.mergeCells('I6:J6'); ws.getCell('I6').value = quoteData.customer.date;
142
+ ws.getCell('A7').value = 'Email:'; ws.mergeCells('B7:E7'); ws.getCell('B7').value = quoteData.customer.email;
143
+ ws.getCell('A8').value = 'Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value = quoteData.customer.addr;
144
+ var hr = ws.getRow(10);
145
+ ['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) {
146
+ 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'} };
147
+ });
148
+ var cr = 11, firstRow = cr;
149
+ quoteData.items.forEach(function(it, i) {
150
+ var row = ws.getRow(cr); row.height = 50;
151
+ row.getCell(1).value = it.stt || i+1;
152
+ row.getCell(3).value = it.name || ''; row.getCell(3).font = { bold:true, size:9 };
153
+ row.getCell(4).value = it.model || '';
154
+ row.getCell(5).value = it.specs || ''; row.getCell(5).font = { size:8, color:{argb:'FF64748B'} };
155
+ row.getCell(6).value = Number(it.qty||1); row.getCell(6).numFmt = '#,##0';
156
+ row.getCell(7).value = Number(it.price||0); row.getCell(7).numFmt = '#,##0"đ"';
157
+ row.getCell(8).value = Number(it.discPrice||it.price||0); row.getCell(8).numFmt = '#,##0"đ"';
158
+ row.getCell(8).font = (it.price||0) > (it.discPrice||0) ? { bold:true, color:{argb:'FFDC3545'} } : {};
159
+ row.getCell(9).value = Number(it.total||0); row.getCell(9).numFmt = '#,##0"đ"'; row.getCell(9).font = { bold:true };
160
+ row.getCell(10).value = it.note || '';
161
+ cr++;
162
+ });
163
+ ws.getRow(cr).height = 28; ws.mergeCells(cr,1,cr,5); ws.getCell('A'+cr).value = 'TỔNG CỘNG';
164
+ ws.getCell('A'+cr).font = { bold:true, size:13, color:{argb:'FFFFFFFF'} }; ws.getCell('A'+cr).fill = { type:'pattern', pattern:'solid', fgColor:{argb:'FF003F62'} };
165
+ ws.getCell('E'+cr).value = { formula:'SUM(I'+firstRow+':I'+(cr-1)+')', result:Number(quoteData.grandTotal||0) };
166
+ ws.getCell('E'+cr).numFmt = '#,##0"đ"'; ws.getCell('E'+cr).font = { bold:true, size:14, color:{argb:'FFF0B840'} };
167
+ wb.calcProperties.fullCalcOnLoad = true;
168
+ var buf = await wb.xlsx.writeBuffer();
169
+ downloadBlob(new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), code + '.xlsx');
170
+ return true;
171
+ } catch (e) {
172
+ alert('❌ Lỗi: ' + e.message);
173
+ console.error('[Auto Inject] exportQuoteExcel error:', e);
174
+ return false;
175
+ }
176
+ }
177
+
178
+ async function exportDeliveryExcel(quoteData, code) {
179
+ if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS'), false;
180
+ try {
181
+ var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Giao hàng');
182
+ ws.views = [{ showGridLines: false }]; ws.columns = [{width:5},{width:34},{width:14},{width:8},{width:20}];
183
+ 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' };
184
+ ws.getCell('A2').value = 'KH: ' + (quoteData.customer.name||''); ws.getCell('D2').value = 'Mã: ' + code;
185
+ ws.getCell('A3').value = 'SĐT: ' + (quoteData.customer.phone||''); ws.getCell('D3').value = 'Ngày: ' + (quoteData.customer.date||'');
186
+ ws.getCell('A4').value = 'Địa chỉ: ' + (quoteData.customer.addr||'');
187
+ var hr = ws.getRow(6); ['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h, i) {
188
+ 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'} };
189
+ });
190
+ var cr = 7, totalQty = 0;
191
+ quoteData.items.forEach(function(it, i) {
192
+ var row = ws.getRow(cr); row.height = 20;
193
+ row.getCell(1).value = it.stt || i+1; row.getCell(2).value = it.name || ''; row.getCell(3).value = it.model || ''; row.getCell(4).value = it.qty || 1; row.getCell(5).value = it.note || '';
194
+ totalQty += Number(it.qty||1); cr++;
195
+ });
196
+ ws.mergeCells(cr,1,cr,3); ws.getCell('A'+cr).value = 'TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D'+cr).value = totalQty;
197
+ var buf = await wb.xlsx.writeBuffer();
198
+ downloadBlob(new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), 'GH-' + code + '.xlsx');
199
+ return true;
200
+ } catch (e) {
201
+ alert('❌ Lỗi: ' + e.message);
202
+ console.error('[Auto Inject] exportDeliveryExcel error:', e);
203
+ return false;
204
+ }
205
+ }
206
+
207
+ // ===== CLICK HANDLER =====
208
+ function handleClick(e) {
209
+ var btn = e.target.closest('button, a');
210
+ if (!btn) return;
211
+ var txt = (btn.textContent || '').toLowerCase();
212
+ // Quote Excel
213
+ if (txt.includes('excel') && !txt.includes('giao') && !txt.includes('gh-')) {
214
+ if (btn.dataset.vaiExporting === '1') return;
215
+ e.preventDefault(); e.stopPropagation();
216
+ setBtnState(btn, true);
217
+ var qd = getQuoteData(); var code = genOrderCode(qd.customer.name);
218
+ exportQuoteExcel(qd, code).then(function() {
219
+ setBtnState(btn, false);
220
+ showToast && showToast('✅ Excel báo giá tải thành công!');
221
+ });
222
+ return false;
223
+ }
224
+ // Delivery Excel
225
+ if ((txt.includes('gh') || txt.includes('giao hàng')) && txt.includes('excel')) {
226
+ if (btn.dataset.vaiExporting === '1') return;
227
+ e.preventDefault(); e.stopPropagation();
228
+ setBtnState(btn, true);
229
+ var order = window.VAI_CURRENT_ORDER || {};
230
+ var ed = {
231
+ customer: { name: order.customer||'', phone: order.phone||'', addr: order.addr||'', date: order.date||'' },
232
+ items: (order.items||[]).map(function(it, i) { 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 }; }),
233
+ grandTotal: order.grandTotal||0
234
+ };
235
+ var ocode = order.code || genOrderCode(order.customer||'');
236
+ exportDeliveryExcel(ed, ocode).then(function() {
237
+ setBtnState(btn, false);
238
+ showToast && showToast('✅ Excel giao hàng tải thành công!');
239
+ });
240
+ return false;
241
+ }
242
+ }
243
+
244
+ // ===== INIT =====
245
+ function init() {
246
+ if (SCRIPT_INJECTED) return;
247
+ document.removeEventListener('click', handleClick, true);
248
+ document.addEventListener('click', handleClick, true);
249
+ SCRIPT_INJECTED = true;
250
+ console.log('[Auto Inject v2.0] Excel fix active - multi-download enabled');
251
+ }
252
+
253
+ // Run immediately
254
+ if (document.readyState === 'loading') {
255
+ document.addEventListener('DOMContentLoaded', init);
256
+ } else {
257
+ init();
258
+ }
259
+ setTimeout(init, 2000);
260
+ setTimeout(init, 5000);
261
+
262
+ window.VAI_AUTO_FIX = { init: init, exportQuoteExcel: exportQuoteExcel, exportDeliveryExcel: exportDeliveryExcel };
263
+ })();
264
+
265
+ console.log('[VAI] Auto Excel Fix v2.0 loaded!');