// ==UserScript== // @name 网页表格导出工具(含数字精度保护) // @namespace http://tampermonkey.net/ // @version 0.32 // @description 为网页表格添加 Excel 导出功能;>11 位数字导出前可选择文本化,防止精度丢失;修复中文标题下划线乱码问题 // @author HiKey // @match *://*/* // @require https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; /* ---------- 样式 ---------- */ GM_addStyle(` .export-btn-group { position: absolute; top: 2px; right: 2px; z-index: 1000; opacity: 0.8; transition: opacity 0.3s; } .export-btn-group:hover { opacity: 1; } .export-btn { padding: 3px 8px; background: rgba(76, 175, 80, 0.7); color: white; border: none; border-radius: 2px; cursor: pointer; font-size: 11px; font-family: Arial; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: background 0.2s; } .export-btn:hover { background: rgba(68, 157, 68, 0.9); } `); /* 安全文件名:保留中文、字母、数字、部分符号,其余变 _,合并连续 _,去头尾,长度 30 */ function safeFileName(rawTitle) { const txt = (rawTitle || 'table').replace(/&[a-zA-Z0-9#]+;/g, ' ').trim(); let s = txt.replace(/[^\u4e00-\u9fa5\u3040-\u309f\u30a0-\u30ff\uac00-\ud7afa-zA-Z0-9\-_. ]/g, '_'); s = s.replace(/_+/g, '_').replace(/^_|_$/g, ''); if (!s) s = 'table'; return s.substring(0, 30); } /* 询问数字处理模式 */ function askNumberMode(hasLong) { if (!hasLong) return 0; const msg = '检测到 >11 位数字,文本化处理可防止表格上数字精度丢失:\n' + '【0】不处理\n' + '【1】仅对 >11 位数字处理\n' + '【2】全部数字处理为文本'; let ans; do { ans = prompt(msg, '1'); if (ans === null) return 0; ans = ans.trim(); } while (!['0', '1', '2'].includes(ans)); return Number(ans); } /* 按选择处理 worksheet */ function processNumbers(ws, mode) { if (mode === 0) return; const range = XLSX.utils.decode_range(ws['!ref']); for (let R = range.s.r; R <= range.e.r; R++) { for (let C = range.s.c; C <= range.e.c; C++) { const cellRef = XLSX.utils.encode_cell({ r: R, c: C }); const cell = ws[cellRef]; if (!cell || cell.t !== 'n') continue; const v = cell.v; if (typeof v !== 'number') continue; const str = String(v); const shouldText = mode === 2 || (mode === 1 && str.replace(/\./, '').length > 11); if (shouldText) { cell.t = 's'; cell.v = str; } } } } /* 导出主函数 */ function exportTable(table) { let hasLong = false; table.querySelectorAll('td, th').forEach(td => { if (hasLong) return; const txt = td.innerText.trim(); if (/^\d+(\.\d+)?$/.test(txt) && txt.replace(/\./, '').length > 11) { hasLong = true; } }); const mode = askNumberMode(hasLong); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.table_to_sheet(table); processNumbers(ws, mode); XLSX.utils.book_append_sheet(wb, ws, '数据'); const title = safeFileName(document.title); const timestamp = new Date().toISOString().slice(0, 10); XLSX.writeFile(wb, `${title}_${timestamp}.xlsx`); } /* 按钮注入与动态监听 */ function processTables() { document.querySelectorAll('table').forEach(table => { if (table.dataset.processed) return; const btnGroup = document.createElement('div'); btnGroup.className = 'export-btn-group'; const btnXLSX = document.createElement('button'); btnXLSX.className = 'export-btn'; btnXLSX.textContent = '导出 Excel'; btnXLSX.title = '点击导出为 Excel 文件'; btnXLSX.addEventListener('click', () => exportTable(table)); btnGroup.appendChild(btnXLSX); const wrapper = document.createElement('div'); wrapper.style.position = 'relative'; wrapper.style.display = 'inline-block'; table.parentNode.insertBefore(wrapper, table); wrapper.appendChild(table); wrapper.appendChild(btnGroup); table.dataset.processed = true; }); } const init = () => { processTables(); new MutationObserver(() => processTables()) .observe(document.body, { childList: true, subtree: true }); }; if (document.readyState === 'complete') init(); else window.addEventListener('load', init); })();