Download auto-patch-gh-excel.js from bep40/V.AISTUDIO: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/bep40/V.AISTUDIO/resolve/refs%2Fpr%2F13/auto-patch-gh-excel.js
- Command line
-
hf download hf://bep40/V.AISTUDIO@refs/pr/13/auto-patch-gh-excel.js
-
curl -L -o auto-patch-gh-excel.js https://huggingface.co/bep40/V.AISTUDIO/resolve/refs%2Fpr%2F13/auto-patch-gh-excel.js
11.3 kB
| /** | |
| * V.AI STUDIO - Auto Patch GH Excel - v2.0 | |
| * | |
| * TỰ ĐỘNG PATCH mà không cần sửa index.html | |
| * Chỉ cần thêm dòng: <script src="auto-patch-gh-excel.js"></script> vào index.html | |
| * | |
| * FIX HOÀN HẢO: | |
| * - exportDeliveryExcel nhận order parameter đúng | |
| * - Click protection (chống bấm nhiều lần) | |
| * - Blob retention đến khi unload (tải nhiều lần được) | |
| * - Hỗ trợ cả Quote & Order modal | |
| */ | |
| (function() { | |
| 'use strict'; | |
| // ===== GLOBAL BLOB REGISTRY ===== | |
| window.__VAI_BLOB_REGISTRY = window.__VAI_BLOB_REGISTRY || { blobs: [], isUnloading: false }; | |
| var reg = window.__VAI_BLOB_REGISTRY; | |
| // Cleanup on unload | |
| function cleanupBlobs() { | |
| reg.isUnloading = true; | |
| reg.blobs.forEach(function(b) { | |
| if (!b.revoked) { | |
| try { URL.revokeObjectURL(b.url); b.revoked = true; } catch(e) {} | |
| } | |
| }); | |
| reg.blobs = []; | |
| } | |
| window.addEventListener('pagehide', cleanupBlobs); | |
| window.addEventListener('beforeunload', cleanupBlobs); | |
| window.addEventListener('unload', cleanupBlobs); | |
| // ===== PATCH URL.revokeObjectURL - CRITICAL ===== | |
| if (!window.__VAI_REVOKE_PATCHED_AUTO) { | |
| var origRevoke = URL.revokeObjectURL; | |
| URL.revokeObjectURL = function(url) { | |
| if (reg.isUnloading || !url || typeof url !== 'string' || !url.startsWith('blob:')) { | |
| return origRevoke.call(this, url); | |
| } | |
| var exists = reg.blobs.find(function(b) { return b.url === url; }); | |
| if (!exists) reg.blobs.push({ url: url, revoked: false, created: Date.now() }); | |
| return undefined; | |
| }; | |
| window.__VAI_REVOKE_PATCHED_AUTO = true; | |
| } | |
| // ===== UTILITIES ===== | |
| function fmt(n) { return n && !isNaN(n) ? Number(n).toLocaleString('vi-VN') + 'đ' : '0đ'; } | |
| function stripDiacritics(s) { return s.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D'); } | |
| function genCode(name) { | |
| var d = new Date(), dd = String(d.getDate()).padStart(2,'0'), mm = String(d.getMonth()+1).padStart(2,'0'), yy = String(d.getFullYear()).slice(-2); | |
| var ini = ''; | |
| if (name && name.trim()) { | |
| name.trim().split(/\s+/).forEach(function(w) { | |
| if (w) { var c = stripDiacritics(w.charAt(0)).toUpperCase(); if (/[A-Z]/.test(c)) ini += c; } | |
| }); | |
| } | |
| return 'VAS' + (ini || 'X') + dd + mm + yy; | |
| } | |
| // ===== BUTTON STATE ===== | |
| function setBtn(b, exporting) { | |
| if (!b) return; | |
| if (exporting) { | |
| b.disabled = true; | |
| b.dataset.vaiExporting = '1'; | |
| b._orig = b.innerHTML; | |
| b.innerHTML = '<span style="opacity:0.7">⏳ Đang xuất...</span>'; | |
| } else { | |
| b.disabled = false; | |
| b.dataset.vaiExporting = '0'; | |
| if (b._orig) b.innerHTML = b._orig; | |
| } | |
| } | |
| // ===== DOWNLOAD BLOB ===== | |
| function dlBlob(blob, filename) { | |
| var url = URL.createObjectURL(blob); | |
| reg.blobs.push({ url: url, revoked: false }); | |
| var a = document.createElement('a'); | |
| a.href = url; a.download = filename; a.style.cssText = 'position:absolute;left:-9999px'; | |
| document.body.appendChild(a); a.click(); | |
| setTimeout(function() { try { document.body.removeChild(a); } catch(e) {} }, 100); | |
| } | |
| // ===== EXPORT QUOTE EXCEL ===== | |
| async function exportQuoteExcel(d, c) { | |
| if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS'), false; | |
| try { | |
| var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Báo giá'); | |
| ws.views = [{ showGridLines: false }]; | |
| ws.columns = [{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}]; | |
| ws.mergeCells('A3:J3'); ws.getCell('A3').value='BẢNG BÁO GIÁ'; | |
| ws.getCell('A3').font = { bold:true, size:18, color:{argb:'FFDB9815'} }; | |
| ws.getCell('A3').alignment = { horizontal:'center' }; | |
| ws.getCell('A5').value='Khách hàng:'; ws.mergeCells('B5:E5'); ws.getCell('B5').value=d.customer?.name||''; | |
| ws.getCell('H5').value='Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value=c; | |
| ws.getCell('A6').value='SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value=d.customer?.phone||''; | |
| ws.getCell('H6').value='Ngày:'; ws.mergeCells('I6:J6'); ws.getCell('I6').value=d.customer?.date||''; | |
| ws.getCell('A7').value='Email:'; ws.mergeCells('B7:E7'); ws.getCell('B7').value=d.customer?.email||''; | |
| ws.getCell('A8').value='Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value=d.customer?.addr||''; | |
| var hr = ws.getRow(10); | |
| ['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){ | |
| 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'}}; | |
| }); | |
| var cr = 11, fr = cr; | |
| (d.items||[]).forEach(function(it,i){ | |
| var r = ws.getRow(cr), bg = i%2===0?'FFF8FAFC':'FFFFFFFF'; r.height=50; | |
| r.getCell(1).value=it.stt||i+1; r.getCell(3).value=it.name||''; r.getCell(3).font={bold:true,size:9}; | |
| r.getCell(4).value=it.model||''; r.getCell(5).value=it.specs||''; r.getCell(5).font={size:8,color:{argb:'FF64748B'}}; | |
| r.getCell(6).value=Number(it.qty||1); r.getCell(6).numFmt='#,##0'; | |
| r.getCell(7).value=Number(it.price||0); r.getCell(7).numFmt='#,##0"đ"'; | |
| r.getCell(8).value=Number(it.discPrice||it.price||0); r.getCell(8).numFmt='#,##0"đ"'; | |
| r.getCell(8).font=(it.price||0)>(it.discPrice||0)?{bold:true,color:{argb:'FFDC3545'}}:{}; | |
| r.getCell(9).value=Number(it.total||0); r.getCell(9).numFmt='#,##0"đ"'; r.getCell(9).font={bold:true}; | |
| r.getCell(10).value=it.note||''; cr++; | |
| }); | |
| ws.getRow(cr).height=28; ws.mergeCells(cr,1,cr,5); ws.getCell('A'+cr).value='TỔNG CỘNG'; | |
| ws.getCell('A'+cr).font={bold:true,size:13,color:{argb:'FFFFFFFF'}}; ws.getCell('A'+cr).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}}; | |
| ws.getCell('E'+cr).value={formula:'SUM(I'+fr+':I'+(cr-1)+')',result:Number(d.grandTotal||0)}; ws.getCell('E'+cr).numFmt='#,##0"đ"'; | |
| ws.getCell('E'+cr).font={bold:true,size:14,color:{argb:'FFF0B840'}}; | |
| wb.calcProperties.fullCalcOnLoad=true; | |
| var buf = await wb.xlsx.writeBuffer(); | |
| dlBlob(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), c+'.xlsx'); | |
| return true; | |
| } catch(e) { console.error('[AutoPatch] exportQuoteExcel error:',e); alert('❌ Lỗi: '+e.message); return false; } | |
| } | |
| // ===== EXPORT DELIVERY EXCEL ===== | |
| async function exportDeliveryExcel(d, c) { | |
| if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS'), false; | |
| try { | |
| var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Giao hàng'); | |
| ws.views = [{ showGridLines: false }]; ws.columns = [{width:5},{width:34},{width:14},{width:8},{width:20}]; | |
| 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'}; | |
| ws.getCell('A2').value='KH: '+(d.customer?.name||''); ws.getCell('D2').value='Mã: '+c; | |
| ws.getCell('A3').value='SĐT: '+(d.customer?.phone||''); ws.getCell('D3').value='Ngày: '+(d.customer?.date||''); | |
| ws.getCell('A4').value='Địa chỉ: '+(d.customer?.addr||''); | |
| var hr = ws.getRow(6); ['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h,i){ | |
| 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'}}; | |
| }); | |
| var cr = 7, tq = 0; | |
| (d.items||[]).forEach(function(it,i){ | |
| var r = ws.getRow(cr); r.height=20; | |
| r.getCell(1).value=it.stt||i+1; r.getCell(2).value=it.name||''; r.getCell(3).value=it.model||''; r.getCell(4).value=it.qty||1; r.getCell(5).value=it.note||''; | |
| tq += Number(it.qty||1); cr++; | |
| }); | |
| ws.mergeCells(cr,1,cr,3); ws.getCell('A'+cr).value='TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D'+cr).value=tq; | |
| var buf = await wb.xlsx.writeBuffer(); | |
| dlBlob(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), 'GH-'+c+'.xlsx'); | |
| return true; | |
| } catch(e) { console.error('[AutoPatch] exportDeliveryExcel error:',e); alert('❌ Lỗi: '+e.message); return false; } | |
| } | |
| // ===== INIT ===== | |
| function init() { | |
| // Check if VAI_EXCEL_FIX v2 already loaded | |
| if (typeof window.VAI_EXCEL_FIX !== 'undefined' && typeof window.VAI_EXCEL_FIX.exportQuoteExcel === 'function') { | |
| console.log('[AutoPatch v2.0] VAI_EXCEL_FIX v2 already loaded - done'); | |
| return; | |
| } | |
| // Load vai-excel-fix-v2.js if not exists | |
| var s = document.createElement('script'); | |
| s.src = '/vai-excel-fix-v2.js?v=' + Date.now(); | |
| s.onload = function() { | |
| console.log('[AutoPatch v2.0] Excel fix v2 loaded'); | |
| }; | |
| s.onerror = function() { | |
| console.warn('[AutoPatch v2.0] Failed to load v2, applying inline patch'); | |
| // Inline fallback patch | |
| if (typeof window.exportDeliveryExcel === 'function') { | |
| var origDE = window.exportDeliveryExcel; | |
| window.exportDeliveryExcel = async function(order, opt) { | |
| var btn = document.activeElement; | |
| if (btn && btn.dataset.vaiExporting === '1') return; | |
| if (btn) setBtn(btn, true); | |
| try { | |
| var d = { customer: { name: order?.customer||'', phone: order?.phone||'', addr: order?.addr||'', date: order?.date||'' }, | |
| 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};}), | |
| grandTotal: order?.grandTotal||0 }; | |
| var c = order?.code || genCode(order?.customer||''); | |
| await exportDeliveryExcel(d, c); | |
| } finally { if (btn) setBtn(btn, false); } | |
| }; | |
| } | |
| }; | |
| document.head.appendChild(s); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init); | |
| } else { | |
| init(); | |
| } | |
| setTimeout(init, 2000); | |
| setTimeout(init, 5000); | |
| })(); |