// ==UserScript==
// @name 表格提取与多格式导出工具(增强版)
// @name:en Table Extraction and Multi Format Export Tool (Enhanced Version)
// @name:zh 表格提取与多格式导出工具(增强版)
// @namespace http://tampermonkey.net/
// @version 1.6.8
// @description 自动检测网页中的表格,支持多种格式导出和快捷键操作,文件名优先使用表格上方的小标题。
// @description:en Automatically detect tables in web pages, support multiple format exports and shortcut key operations, and prioritize using subheadings above the table for file names.
// @author Will
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// @license MIT
// @homepage https://github.com/jwq2011/TamperMonkey-Scripts
// @supportURL https://github.com/jwq2011/TamperMonkey-Scripts/issues
// @downloadURL https://update.greasyfork.icu/scripts/542879/%E8%A1%A8%E6%A0%BC%E6%8F%90%E5%8F%96%E4%B8%8E%E5%A4%9A%E6%A0%BC%E5%BC%8F%E5%AF%BC%E5%87%BA%E5%B7%A5%E5%85%B7%EF%BC%88%E5%A2%9E%E5%BC%BA%E7%89%88%EF%BC%89.user.js
// @updateURL https://update.greasyfork.icu/scripts/542879/%E8%A1%A8%E6%A0%BC%E6%8F%90%E5%8F%96%E4%B8%8E%E5%A4%9A%E6%A0%BC%E5%BC%8F%E5%AF%BC%E5%87%BA%E5%B7%A5%E5%85%B7%EF%BC%88%E5%A2%9E%E5%BC%BA%E7%89%88%EF%BC%89.meta.js
// ==/UserScript==
(function () {
"use strict";
// 初始化默认设置
const defaultSettings = {
showGlobalButton: false, // 默认不显示全局按钮
};
// 获取用户设置(如果不存在,则使用默认值)
let settings;
try {
const storedSettings = GM_getValue("settings", "{}");
settings = Object.assign({}, defaultSettings, typeof storedSettings === "string" ? JSON.parse(storedSettings) : storedSettings);
} catch (error) {
console.error("解析用户设置失败:", error);
settings = Object.assign({}, defaultSettings);
}
console.log("加载设置:", settings);
// 保存用户设置
const saveSettings = () => {
try {
GM_setValue("settings", JSON.stringify(settings));
console.log("保存设置:", settings);
} catch (error) {
console.error("保存用户设置失败:", error);
}
};
// 创建 Tampermonkey 设置命令
GM_registerMenuCommand("设置 - 显示全局按钮", () => {
const userInput = confirm(
"是否显示全局“提取所有表格”按钮?\n当前状态:" +
(settings.showGlobalButton ? "已启用" : "已禁用")
);
settings.showGlobalButton = userInput;
saveSettings();
// 根据设置决定是否显示按钮
if (settings.showGlobalButton) {
createGlobalExtractButton();
} else {
const existingButton = document.getElementById("global-extract-button");
if (existingButton) {
existingButton.remove();
console.log("移除了全局按钮");
}
}
});
// 注册右键菜单命令
GM_registerMenuCommand("提取所有表格", () => {
console.log("执行提取所有表格命令");
extractAllTables();
});
// 添加样式
GM_addStyle(`
.table-extract-button {
position: absolute;
background-color: #4CAF50;
color: white;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
z-index: 9999;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
font-size: 12px;
display: none; /* 默认隐藏 */
}
#global-extract-button {
position: fixed;
top: 20px;
right: 20px;
background-color: #007BFF;
color: white;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
z-index: 10000;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
font-size: 14px;
width: auto; /* 自适应宽度 */
max-width: 200px; /* 最大宽度限制 */
text-align: center;
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 省略号表示超出内容 */
}
.export-menu,
.preview-window {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
padding: 20px;
border: 1px solid #ccc;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 10000;
max-height: 80vh;
overflow-y: auto;
}
.preview-window {
z-index: 10001;
}
.preview-window table {
border-collapse: collapse;
width: 100%;
max-width: 80vw;
}
.preview-window td {
padding: 5px;
border: 1px solid #ddd;
}
.status-message {
margin-top: 10px;
font-size: 14px;
color: green;
}
.error-message {
margin-top: 10px;
font-size: 14px;
color: red;
}
`);
// 批量提取所有表格
const extractAllTables = () => {
console.log("执行批量提取所有表格逻辑");
const tables = Array.from(document.querySelectorAll("table")).filter((table) => {
const rows = table.querySelectorAll("tr");
return rows.length >= 2 && Array.from(rows).some((row) => row.children.length >= 2);
});
if (tables.length === 0) {
alert("未找到符合条件的表格!");
console.warn("未找到符合条件的表格");
return;
}
const allData = tables.flatMap((table) => {
const rows = Array.from(table.querySelectorAll("tr"));
return rows.map((row) =>
Array.from(row.querySelectorAll("td, th")).map((cell) => cell.innerText.trim())
);
});
showBatchExportMenu(allData); // 调用批量导出菜单
};
// 显示批量导出菜单
const showBatchExportMenu = (data) => {
console.log("显示批量导出菜单...");
// 移除旧的批量导出菜单
const existingMenu = document.getElementById("batch-export-menu");
if (existingMenu) {
existingMenu.remove();
console.log("移除了旧的批量导出菜单");
}
// 创建新的批量导出菜单
const menu = document.createElement("div");
menu.id = "batch-export-menu";
menu.className = "export-menu";
menu.innerHTML = `
批量导出选项:
`;
document.body.appendChild(menu);
// 逐个导出(暂未实现)
document.getElementById("export-all-separate").addEventListener("click", () => {
alert("逐个导出暂未实现!");
menu.remove();
});
// 合并导出
document.getElementById("export-all-merged").addEventListener("click", () => {
exportData(data, "excel", "merged_tables").then((success) => {
if (success) {
alert("所有表格已合并导出!");
} else {
alert("导出失败,请检查控制台错误信息!");
}
menu.remove();
});
});
// 关闭按钮事件绑定
const closeBtn = document.getElementById("close-batch-menu-btn");
if (closeBtn) {
closeBtn.addEventListener("click", () => {
console.log("点击关闭按钮:关闭批量导出菜单");
menu.remove();
});
} else {
console.warn("未找到批量导出菜单的关闭按钮");
}
};
// 提取单个表格数据
const extractTableData = (table) => {
const rows = Array.from(table.querySelectorAll("tr"));
const data = rows.map((row) => {
return Array.from(row.querySelectorAll("td, th")).map((cell) => cell.innerText.trim());
});
showExportMenu(data, guessTableName(table), table);
};
// 显示导出菜单
const showExportMenu = (data, filename, table) => {
console.log("显示导出菜单...");
// 移除旧的导出菜单
const existingMenu = document.getElementById("export-menu");
if (existingMenu) {
existingMenu.remove();
console.log("移除了旧的导出菜单");
}
// 创建新的导出菜单
const menu = document.createElement("div");
menu.id = "export-menu";
menu.className = "export-menu";
menu.innerHTML = `
选择导出格式:
`;
document.body.appendChild(menu);
// 绑定导出按钮事件
document.querySelectorAll(".export-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const format = btn.dataset.format;
exportData(data, format, filename, table).then((success) => {
const statusMessage = document.getElementById("status-message");
if (success) {
statusMessage.textContent = `导出成功:${format.toUpperCase()} 文件已生成!`;
statusMessage.className = "status-message";
} else {
statusMessage.textContent = `导出失败:请检查控制台错误信息!`;
statusMessage.className = "error-message";
}
});
});
});
// 数据预览按钮事件绑定
const previewBtn = document.getElementById("preview-data-btn");
if (previewBtn) {
previewBtn.addEventListener("click", () => {
console.log("点击数据预览按钮:触发预览功能");
showPreviewWindow(data);
});
} else {
console.warn("未找到数据预览按钮");
}
// 复制到剪贴板(原格式)
const copyOriginalBtn = document.getElementById("copy-original-btn");
if (copyOriginalBtn) {
copyOriginalBtn.addEventListener("click", () => {
console.log("点击复制到剪贴板(原格式)");
copyToClipboard(data, "original");
});
}
// 复制到剪贴板(Markdown)
const copyMarkdownBtn = document.getElementById("copy-markdown-btn");
if (copyMarkdownBtn) {
copyMarkdownBtn.addEventListener("click", () => {
console.log("点击复制到剪贴板(Markdown)");
copyToClipboard(data, "markdown");
});
}
// 关闭按钮事件绑定
const closeBtn = document.getElementById("close-export-menu-btn");
if (closeBtn) {
closeBtn.addEventListener("click", () => {
console.log("点击关闭按钮:关闭导出菜单");
menu.remove();
});
} else {
console.warn("未找到导出菜单的关闭按钮");
}
};
// 显示数据预览窗口
const showPreviewWindow = (data) => {
console.log("显示数据预览窗口");
// 移除旧的预览窗口
const existingPreview = document.getElementById("preview-window");
if (existingPreview) {
existingPreview.remove();
console.log("移除了旧的数据预览窗口");
}
// 创建新的预览窗口
const previewWindow = document.createElement("div");
previewWindow.id = "preview-window";
previewWindow.className = "preview-window";
previewWindow.innerHTML = `
数据预览
${data.map((row) => `${row.map((cell) => `${cell} | `).join("")}
`).join("")}
`;
document.body.appendChild(previewWindow);
// 关闭按钮事件绑定
const closeBtn = document.getElementById("close-preview-window-btn");
if (closeBtn) {
closeBtn.addEventListener("click", () => {
console.log("点击关闭按钮:关闭数据预览窗口");
previewWindow.remove();
});
} else {
console.warn("未找到数据预览窗口的关闭按钮");
}
};
// 导出数据
const exportData = async (data, format, filename, table) => {
try {
let finalFilename;
switch (format) {
case "json":
finalFilename = `${filename}.json`;
saveFile(JSON.stringify(data, null, 2), finalFilename, "application/json");
break;
case "csv":
finalFilename = `${filename}.csv`;
saveFile(toCSV(data), finalFilename, "text/csv");
break;
case "excel":
finalFilename = `${filename}.xlsx`;
saveExcel(data, finalFilename);
break;
case "markdown":
finalFilename = `${filename}.md`;
saveFile(toMarkdown(data), finalFilename, "text/plain");
break;
case "sql":
finalFilename = `${filename}.sql`;
saveFile(toSQL(data), finalFilename, "text/plain");
break;
case "html":
finalFilename = `${filename}.html`;
saveHTMLWithStyle(table, finalFilename);
break;
case "pdf-formatted":
finalFilename = `${filename}.pdf`;
await savePDFFormatted(table, finalFilename);
break;
case "pdf-text":
finalFilename = `${filename}.pdf`;
await savePDFText(data, finalFilename);
break;
default:
alert("不支持的格式!");
return false;
}
return true; // 成功
} catch (error) {
console.error("导出失败:", error);
return false; // 失败
}
};
// 转换为 CSV 格式
const toCSV = (data) => {
return data.map((row) => row.join(",")).join("\n");
};
// 转换为 Markdown 格式
const toMarkdown = (data) => {
const header = data[0];
const body = data.slice(1);
const headerLine = "|" + header.join("|") + "|";
const separator = "|" + header.map(() => "---").join("|") + "|";
const bodyLines = body.map((row) => "|" + row.join("|") + "|");
return [headerLine, separator, ...bodyLines].join("\n");
};
// 转换为 SQL 格式
const toSQL = (data) => {
const tableName = "my_table";
const columns = data[0];
const values = data.slice(1).map((row) =>
row.map((value) => `"${value.replace(/"/g, '\\"')}"`).join(", ")
);
const insertStatements = values.map((val) => `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${val});`);
return insertStatements.join("\n");
};
// 保存为 HTML 文件(保留表格样式)
const saveHTMLWithStyle = (table, filename) => {
const cloneTable = table.cloneNode(true); // 克隆表格节点
const wrapper = document.createElement("div");
wrapper.appendChild(cloneTable);
const htmlContent = wrapper.innerHTML;
saveFile(htmlContent, `${filename}.html`, "text/html");
};
// 保存为 PDF(带格式)
const savePDFFormatted = (table, filename) => {
return new Promise((resolve, reject) => {
html2canvas(table).then((canvas) => {
const imgData = canvas.toDataURL("image/png");
const pdf = new jspdf.jsPDF();
const imgWidth = 210; // A4 宽度
const pageHeight = 297; // A4 高度
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
pdf.addPage();
pdf.addImage(imgData, "PNG", 0, -pageHeight + heightLeft, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
pdf.save(`${filename}`);
resolve();
}).catch(reject);
});
};
// 保存为 PDF(纯文本)
const savePDFText = (data, filename) => {
return new Promise((resolve, reject) => {
const pdf = new jspdf.jsPDF();
pdf.addFont("https://cdn.jsdelivr.net/gh/fengyuanchen/jsdocx@master/fonts/MicrosoftYaHei-normal.ttf", "MicrosoftYaHei", "normal");
pdf.setFont("MicrosoftYaHei");
const pageHeight = 297; // A4 高度
let currentHeight = 10;
data.forEach((row) => {
const line = row.join(" | ");
if (currentHeight > pageHeight - 10) {
pdf.addPage();
currentHeight = 10;
}
pdf.text(line, 10, currentHeight);
currentHeight += 10;
});
pdf.save(`${filename}`);
resolve();
});
};
// 复制到剪贴板
const copyToClipboard = (data, format) => {
let content;
if (format === "markdown") {
const header = data[0];
const body = data.slice(1);
const headerLine = "|" + header.join("|") + "|";
const separator = "|" + header.map(() => "---").join("|") + "|";
const bodyLines = body.map((row) => "|" + row.join("|") + "|");
content = [headerLine, separator, ...bodyLines].join("\n");
} else {
content = data.map((row) => row.join("\t")).join("\n"); // 原格式(Tab 分隔)
}
navigator.clipboard.writeText(content).then(() => {
alert("内容已复制到剪贴板!");
}).catch((error) => {
console.error("复制失败:", error);
alert("复制失败,请检查控制台!");
});
};
// 保存文件
const saveFile = (content, filename, mimeType) => {
const blob = new Blob([content], { type: mimeType });
saveAs(blob, filename);
};
// 保存 Excel 文件
const saveExcel = (data, filename) => {
try {
const worksheet = XLSX.utils.aoa_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
XLSX.writeFile(workbook, filename);
} catch (error) {
console.error("Excel 导出失败:", error);
alert(`Excel 导出失败,请检查控制台:${error.message}`);
}
};
// 猜测表格的小标题名称
const guessTableName = (table) => {
let parent = table.parentElement;
while (parent && parent.tagName !== "BODY") {
const header = parent.querySelector("h1, h2, h3, p, span, div");
if (header && header.innerText.trim()) {
return header.innerText.trim().replace(/\s+/g, "_").replace(/[^\w]/g, "");
}
parent = parent.parentElement;
}
return "table"; // 默认文件名
};
// 创建全局提取按钮
const createGlobalExtractButton = () => {
if (!settings.showGlobalButton) {
console.log("用户未启用全局按钮,跳过创建");
return;
}
// 如果按钮已存在,先移除
const existingButton = document.getElementById("global-extract-button");
if (existingButton) {
existingButton.remove();
console.log("移除旧的全局按钮");
}
const button = document.createElement("div");
button.id = "global-extract-button";
button.textContent = "提取所有表格";
button.style.position = "fixed";
button.style.top = "20px";
button.style.right = "20px";
button.style.zIndex = "10000";
// 将函数绑定到按钮点击事件
button.addEventListener("click", () => {
console.log("点击全局按钮:触发提取所有表格");
extractAllTables();
});
document.body.appendChild(button);
console.log("创建新的全局按钮");
// 拖动功能
let isDragging = false;
let offsetX = 0;
let offsetY = 0;
button.addEventListener("mousedown", (e) => {
console.log("开始拖动全局按钮");
isDragging = true;
offsetX = e.clientX - button.getBoundingClientRect().left;
offsetY = e.clientY - button.getBoundingClientRect().top;
});
document.addEventListener("mousemove", (e) => {
if (isDragging) {
console.log("拖动全局按钮到新位置");
const newX = Math.min(Math.max(e.clientX - offsetX, 0), window.innerWidth - button.offsetWidth);
const newY = Math.min(Math.max(e.clientY - offsetY, 0), window.innerHeight - button.offsetHeight);
button.style.left = `${newX}px`;
button.style.top = `${newY}px`;
}
});
document.addEventListener("mouseup", () => {
console.log("结束拖动全局按钮");
isDragging = false;
});
};
// 自动检测表格并添加提取按钮
const detectTables = () => {
console.log("检测页面中的表格...");
const tables = Array.from(document.querySelectorAll("table")).filter((table) => {
const rows = table.querySelectorAll("tr");
return rows.length >= 2 && Array.from(rows).some((row) => row.children.length >= 2);
});
if (tables.length === 0) {
console.warn("未找到符合条件的表格");
} else {
console.log(`检测到 ${tables.length} 个表格`);
}
tables.forEach((table) => {
createExtractButton(table);
});
};
// 创建单个表格提取按钮
const createExtractButton = (table) => {
const button = document.createElement("div");
button.textContent = "提取表格";
button.classList.add("table-extract-button");
// 固定按钮位置
const rect = table.getBoundingClientRect();
button.style.top = `${rect.top + window.scrollY}px`;
button.style.left = `${rect.right + window.scrollX + 10}px`;
document.body.appendChild(button);
let hideTimeout;
// 鼠标进入表格范围时显示按钮
table.addEventListener("mouseenter", () => {
clearTimeout(hideTimeout);
button.style.display = "block";
console.log("显示表格提取按钮");
});
// 鼠标离开表格范围时延迟隐藏按钮
table.addEventListener("mouseleave", () => {
hideTimeout = setTimeout(() => {
button.style.display = "none";
console.log("隐藏表格提取按钮");
}, 1000);
});
// 添加点击事件
button.addEventListener("click", () => {
console.log("点击表格提取按钮:触发提取表格数据");
extractTableData(table);
});
};
// 页面加载完成后执行
window.addEventListener("load", () => {
console.log("页面加载完成,初始化脚本");
// 根据设置决定是否显示全局按钮
if (settings.showGlobalButton) {
console.log("用户启用了全局按钮,尝试创建按钮");
createGlobalExtractButton();
} else {
console.log("用户未启用全局按钮,跳过创建");
}
// 检测表格并添加提取按钮
detectTables();
});
// 快捷键支持
hotkeys("alt+e", (event) => {
event.preventDefault();
const targetTable = document.querySelector("table:hover");
if (targetTable) {
extractTableData(targetTable);
} else {
alert("请将鼠标悬停在一个表格上!");
}
});
})();