// ==UserScript==
// @name Table 导出 CSV 右键菜单
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 为页面中的table元素添加右键菜单,支持导出为CSV文件
// @author newbieking
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/564514/Table%20%E5%AF%BC%E5%87%BA%20CSV%20%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95.user.js
// @updateURL https://update.greasyfork.icu/scripts/564514/Table%20%E5%AF%BC%E5%87%BA%20CSV%20%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95.meta.js
// ==/UserScript==
(function() {
'use strict';
// 1. 定义全局变量,存储右键点击的table元素
let targetTable = null;
// 2. 添加右键菜单的样式,保证菜单美观且不被页面样式干扰
GM_addStyle(`
#newbieking-table-csv-menu {
position: fixed;
z-index: 999999;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 2px 2px 8px rgba(0,0,0,0.2);
padding: 5px 0;
display: none;
min-width: 120px;
font-size: 14px;
}
#newbieking-table-csv-menu li {
list-style: none;
padding: 6px 15px;
cursor: pointer;
}
#newbieking-table-csv-menu li:hover {
background-color: #f0f0f0;
}
`);
// 3. 创建右键菜单DOM元素
function createContextMenu() {
// 避免重复创建菜单
if (document.getElementById('newbieking-table-csv-menu')) return;
const menu = document.createElement('ul');
menu.id = 'newbieking-table-csv-menu';
menu.innerHTML = `
导出为CSV文件
`;
document.body.appendChild(menu);
// 绑定菜单点击事件
document.getElementById('export-table-csv').addEventListener('click', exportTableToCSV);
// 点击页面其他区域关闭菜单
document.addEventListener('click', (e) => {
if (!menu.contains(e.target)) {
menu.style.display = 'none';
}
});
}
// 4. 提取Table数据并转换为CSV格式
function tableToCSV(table) {
const csvRows = [];
const rows = table.querySelectorAll('tr');
// 遍历每一行
for (const row of rows) {
const csvCells = [];
const cells = row.querySelectorAll('th, td');
// 遍历每个单元格,处理特殊字符(逗号、换行、引号)
for (const cell of cells) {
// 获取单元格纯文本,去除多余空格和换行
let cellText = cell.textContent.trim().replace(/\s+/g, ' ');
// 处理CSV特殊字符:包含逗号/引号/换行时,用双引号包裹,内部双引号转义为两个
if (cellText.includes(',') || cellText.includes('"') || cellText.includes('\n')) {
cellText = `"${cellText.replace(/"/g, '""')}"`;
}
csvCells.push(cellText);
}
// 单元格用逗号分隔,组成一行
csvRows.push(csvCells.join(','));
}
// 行之间用换行分隔,生成完整CSV内容
return csvRows.join('\n');
}
// 5. 导出CSV文件的核心函数
function exportTableToCSV() {
if (!targetTable) return;
// 提取CSV数据
const csvContent = tableToCSV(targetTable);
// 创建Blob对象(UTF-8编码,解决中文乱码)
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
// 创建下载链接
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
// 设置文件名(用页面标题+时间戳,避免重复)
const fileName = `${document.title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_')}_${new Date().getTime()}.csv`;
link.setAttribute('href', url);
link.setAttribute('download', fileName);
link.style.display = 'none';
document.body.appendChild(link);
// 触发下载
link.click();
// 清理资源
document.body.removeChild(link);
URL.revokeObjectURL(url);
// 关闭菜单
document.getElementById('newbieking-table-csv-menu').style.display = 'none';
}
// 6. 监听页面右键事件,只在table上显示自定义菜单
document.addEventListener('contextmenu', (e) => {
// 判断右键目标是否是table,或table的子元素(td/th/tr)
const table = e.target.closest('table');
if (table) {
// 阻止默认右键菜单
e.preventDefault();
// 记录当前目标table
targetTable = table;
// 创建菜单(首次执行)
createContextMenu();
// 显示菜单,定位到鼠标位置
const menu = document.getElementById('newbieking-table-csv-menu');
menu.style.display = 'block';
menu.style.left = `${e.clientX}px`;
menu.style.top = `${e.clientY}px`;
// 处理菜单超出页面边界的情况(向右/向下超出时调整位置)
const menuRect = menu.getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
if (menuRect.right > windowWidth) {
menu.style.left = `${e.clientX - menuRect.width}px`;
}
if (menuRect.bottom > windowHeight) {
menu.style.top = `${e.clientY - menuRect.height}px`;
}
} else {
// 非table区域,隐藏自定义菜单
const menu = document.getElementById('newbieking-table-csv-menu');
if (menu) menu.style.display = 'none';
}
});
})();