TOOLS为非官方社区,欢迎一起建设,答疑群:717421103
全站通知:
油猴工具/批量回退及预览差异
刷
历
编
阅读
2026-04-27更新
最新编辑:WIKI不易猫猫叹气
阅读:
更新日期:2026-04-27
最新编辑:WIKI不易猫猫叹气
< 油猴工具
跳到导航
跳到搜索
// ==UserScript==
// @name 批量回退及预览差异
// @namespace http://tampermonkey.net/
// @version 2025-08-26
// @description 用于bwiki的[[特殊:最近更改]]页面,支持多选记录进行批量回退和悬浮预览差异,按需魔改
// @author AI、长亭
// @match *://wiki.biligame.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
console.log('===[批量回退工具] 脚本===');
// 等待页面加载完成
if (document.readyState === 'loading') {
console.log('[批量回退工具] 页面加载中,等待DOM加载完成');
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
/**
* 初始化函数,设置所有功能
*/
function init() {
// 尝试获取目标列表(支持两种结构)
let targetElement = document.querySelector('ul.mw-contributions-list');
let isTableStructure = false;
// 如果没找到ul列表,尝试查找表格结构
if (!targetElement) {
targetElement = document.querySelector('.mw-changeslist');
if (targetElement) {
isTableStructure = true;
console.log('[批量回退工具] 找到表格结构的编辑历史');
} else {
console.log('[批量回退工具] 未找到编辑历史元素');
return;
}
}
// 为符合条件的元素添加勾选框
addCheckboxes(targetElement, isTableStructure);
// 添加批量操作按钮
addBatchButtons();
// 添加差异悬停预览功能
addDiffHoverEffect();
}
/**
* 为符合条件的元素添加勾选框
* @param {HTMLElement} container - 目标容器元素
* @param {boolean} isTableStructure - 是否为表格结构
*/
function addCheckboxes(container, isTableStructure) {
if (isTableStructure) {
// 处理表格结构
const tableRows = container.querySelectorAll('table.mw-changeslist-line tr');
tableRows.forEach(tr => {
// 只处理包含mw-rollback-link的行
if (tr.querySelector('.mw-rollback-link')) {
// 找到第一个单元格
const firstCell = tr.querySelector('td');
if (firstCell) {
// 创建勾选框
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'batch-rollback-checkbox';
checkbox.style.marginRight = '10px';
// 将勾选框添加到单元格的开头
firstCell.insertBefore(checkbox, firstCell.firstChild);
}
}
});
} else {
// 处理列表结构
const listItems = container.querySelectorAll('li');
listItems.forEach(li => {
// 只处理包含mw-rollback-link的li
if (li.querySelector('.mw-rollback-link')) {
// 创建勾选框
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'batch-rollback-checkbox';
checkbox.style.marginRight = '10px';
// 将勾选框添加到li的开头
li.insertBefore(checkbox, li.firstChild);
}
});
}
}
/**
* 添加批量操作按钮(批量勾选和批量回退)
*/
function addBatchButtons() {
// 创建按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.className = 'batch-rollback-container';
buttonContainer.style.position = 'fixed';
buttonContainer.style.bottom = '20px';
buttonContainer.style.right = '20px';
buttonContainer.style.zIndex = '9999';
buttonContainer.style.display = 'flex';
buttonContainer.style.flexDirection = 'column';
buttonContainer.style.gap = '10px';
// 创建批量勾选按钮
const selectAllButton = document.createElement('button');
selectAllButton.className = 'batch-select-button';
selectAllButton.textContent = '批量勾选';
selectAllButton.style.padding = '8px 15px';
selectAllButton.style.backgroundColor = '#4CAF50';
selectAllButton.style.color = 'white';
selectAllButton.style.border = 'none';
selectAllButton.style.borderRadius = '4px';
selectAllButton.style.cursor = 'pointer';
selectAllButton.style.fontWeight = 'bold';
// 批量勾选按钮状态(0: 默认, 1: 全选)
let selectState = 0;
// 批量勾选按钮点击事件
selectAllButton.addEventListener('click', () => {
const checkboxes = document.querySelectorAll('.batch-rollback-checkbox');
if (selectState === 0) {
// 全选
checkboxes.forEach(checkbox => {
checkbox.checked = true;
});
selectAllButton.textContent = '取消全选';
selectState = 1;
} else {
// 取消全选
checkboxes.forEach(checkbox => {
checkbox.checked = false;
});
selectAllButton.textContent = '批量勾选';
selectState = 0;
}
});
// 创建批量回退按钮
const batchRollbackButton = document.createElement('button');
batchRollbackButton.className = 'batch-rollback-button';
batchRollbackButton.textContent = '批量回退';
batchRollbackButton.style.padding = '8px 15px';
batchRollbackButton.style.backgroundColor = '#f44336';
batchRollbackButton.style.color = 'white';
batchRollbackButton.style.border = 'none';
batchRollbackButton.style.borderRadius = '4px';
batchRollbackButton.style.cursor = 'pointer';
batchRollbackButton.style.fontWeight = 'bold';
// 批量回退按钮点击事件
batchRollbackButton.addEventListener('click', () => {
performBatchRollback();
});
// 添加进度条容器
const progressContainer = document.createElement('div');
progressContainer.className = 'batch-rollback-progress-container';
progressContainer.style.width = '200px';
progressContainer.style.height = '20px';
progressContainer.style.backgroundColor = '#f1f1f1';
progressContainer.style.borderRadius = '10px';
progressContainer.style.overflow = 'hidden';
progressContainer.style.display = 'none';
// 添加进度条
const progressBar = document.createElement('div');
progressBar.className = 'batch-rollback-progress-bar';
progressBar.style.height = '100%';
progressBar.style.backgroundColor = '#4CAF50';
progressBar.style.width = '0%';
progressBar.style.transition = 'width 0.3s ease';
// 添加进度文本
const progressText = document.createElement('div');
progressText.className = 'batch-rollback-progress-text';
progressText.style.position = 'absolute';
progressText.style.width = '200px';
progressText.style.textAlign = 'center';
progressText.style.lineHeight = '20px';
progressText.style.color = '#000';
progressText.style.fontSize = '12px';
progressText.textContent = '0%';
// 组装进度条
progressContainer.appendChild(progressBar);
progressContainer.appendChild(progressText);
// 创建预加载进度显示文本
const preloadProgressText = document.createElement('div');
preloadProgressText.className = 'preload-progress-text';
preloadProgressText.style.textAlign = 'center';
preloadProgressText.style.color = '#333';
preloadProgressText.style.fontSize = '14px';
preloadProgressText.textContent = '差异预加载 0/0';
// 组装按钮容器
buttonContainer.appendChild(progressContainer);
buttonContainer.appendChild(selectAllButton);
buttonContainer.appendChild(batchRollbackButton);
buttonContainer.appendChild(preloadProgressText);
// 添加到页面
document.body.appendChild(buttonContainer);
}
/**
* 执行批量回退操作
*/
function performBatchRollback() {
const checkboxes = document.querySelectorAll('.batch-rollback-checkbox:checked');
if (checkboxes.length === 0) {
alert('请先选择要回退的编辑');
return;
}
// 获取进度条元素
const progressContainer = document.querySelector('.batch-rollback-progress-container');
const progressBar = document.querySelector('.batch-rollback-progress-bar');
const progressText = document.querySelector('.batch-rollback-progress-text');
// 显示进度条
progressContainer.style.display = 'block';
progressBar.style.width = '0%';
progressText.textContent = '0%';
// 计算每次回退后进度条增加的百分比
const progressIncrement = 100 / checkboxes.length;
let currentProgress = 0;
// 递归函数执行回退操作
function processNext(index) {
if (index >= checkboxes.length) {
// 所有回退完成
progressBar.style.width = '100%';
progressText.textContent = '100%';
setTimeout(() => {
progressContainer.style.display = 'none';
alert('批量回退操作已完成');
}, 500);
return;
}
// 获取当前勾选框所在的行(支持li或tr)
const parentElement = checkboxes[index].closest('li, tr');
if (parentElement) {
// 找到回退按钮
const rollbackLink = parentElement.querySelector('.mw-rollback-link a');
if (rollbackLink) {
// 在新标签页打开回退链接并监控状态
// 创建隐藏iframe执行回退操作
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
// iframe加载完成后处理
iframe.onload = function() {
// 删除iframe元素
document.body.removeChild(iframe);
// 更新进度条
currentProgress += progressIncrement;
progressBar.style.width = `${currentProgress}%`;
progressText.textContent = `${Math.round(currentProgress)}%`;
// 设置延迟以确保操作完成
setTimeout(() => {
processNext(index + 1);
}, 1000);
};
// 设置iframe源为回退链接
iframe.src = rollbackLink.href;
return;
}
}
// 等待1秒后处理下一个
setTimeout(() => {
processNext(index + 1);
}, 1000);
}
// 开始处理第一个
processNext(0);
}
/**
* 为差异按钮添加鼠标悬停预览效果
*/
function addDiffHoverEffect() {
/**
* 预加载差异内容
* @param {HTMLAnchorElement} diffLink - 差异链接元素
*/
function preloadDiffContent(diffLink) {
const url = diffLink.href;
if (diffCache.has(url) || loadingUrls.has(url)) return;
loadingUrls.add(url);
// 创建隐藏iframe加载差异内容
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
iframe.onload = () => {
try {
const diffTable = iframe.contentDocument.querySelector('table.diff');
if (diffTable) {
// 存储完整的表格HTML
diffCache.set(url, diffTable.outerHTML);
}
} catch (e) {
console.error('[批量回退工具] 预加载差异内容失败:', e);
}
loadingUrls.delete(url);
iframe.remove();
};
// 错误处理
iframe.onerror = () => {
loadingUrls.delete(url);
iframe.remove();
};
document.body.appendChild(iframe);
}
/**
* 初始化差异内容预加载机制
*/
function initDiffPreloading() {
// 计算总链接数
const allDiffLinks = document.querySelectorAll('a.mw-changeslist-diff');
totalLinks = allDiffLinks.length;
updatePreloadProgress();
// 使用Intersection Observer监测元素进入视口
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const diffLink = entry.target.querySelector('a.mw-changeslist-diff');
if (diffLink && !diffCache.has(diffLink.href) && !loadingUrls.has(diffLink.href) && !preloadQueue.includes(diffLink)) {
preloadQueue.push(diffLink);
processPreloadQueue(); // 触发队列处理
}
// 添加到队列后取消观察,避免重复添加
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '2000px 0px', // 提前2000px开始预加载(扩大预加载范围)
threshold: 0
});
// 将所有差异按钮容器添加到观察列表
document.querySelectorAll('.mw-changeslist-links').forEach(container => {
observer.observe(container);
});
}
// 缓存差异内容的对象,键为URL,值为差异表格HTML
const diffCache = new Map();
// 存储正在加载的URL,避免重复请求
const loadingUrls = new Set();
// 预加载队列
const preloadQueue = [];
// 获取预加载进度显示元素
const preloadProgressText = document.querySelector('.preload-progress-text');
// 总链接数
let totalLinks = 0;
// 已加载链接数
let loadedLinks = 0;
// 初始化预加载机制
initDiffPreloading();
// 是否正在加载中
let isLoading = false;
/**
* 处理预加载队列,串行加载差异内容
*/
function processPreloadQueue() {
if (isLoading || preloadQueue.length === 0) return;
isLoading = true;
const diffLink = preloadQueue.shift();
if (!diffLink) {
isLoading = false;
return;
}
preloadDiffContent(diffLink).then(() => {
loadedLinks++;
updatePreloadProgress();
isLoading = false;
processPreloadQueue(); // 加载完成后处理下一个
}).catch(() => {
isLoading = false;
processPreloadQueue(); // 出错也继续处理下一个
});
}
/**
* 更新预加载进度显示
*/
function updatePreloadProgress() {
if (preloadProgressText) {
preloadProgressText.textContent = `差异预加载 ${loadedLinks}/${totalLinks}`;
}
}
/**
* 预加载差异内容(返回Promise)
* @param {HTMLAnchorElement} diffLink - 差异链接元素
*/
function preloadDiffContent(diffLink) {
return new Promise((resolve, reject) => {
const url = diffLink.href;
if (diffCache.has(url) || loadingUrls.has(url)) {
resolve();
return;
}
loadingUrls.add(url);
// 创建隐藏iframe加载差异内容
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
iframe.onload = () => {
try {
const diffTable = iframe.contentDocument.querySelector('table.diff');
if (diffTable) {
// 存储完整的表格HTML
diffCache.set(url, diffTable.outerHTML);
}
resolve();
} catch (e) {
console.error('[批量回退工具] 预加载差异内容失败:', e);
reject(e);
} finally {
loadingUrls.delete(url);
iframe.remove();
}
};
// 错误处理
iframe.onerror = () => {
loadingUrls.delete(url);
iframe.remove();
reject(new Error('Failed to load iframe'));
};
document.body.appendChild(iframe);
});
}
// 为所有差异按钮容器添加事件监听
document.querySelectorAll('.mw-changeslist-links').forEach(linkContainer => {
const diffLink = linkContainer.querySelector('a.mw-changeslist-diff');
if (!diffLink) return;
let infobox = null;
let iframe = null;
// 鼠标悬停时创建预览框
linkContainer.addEventListener('mouseenter', (e) => {
// 创建信息框元素
infobox = document.createElement('div');
infobox.className = 'diff-preview-infobox';
infobox.style.cssText = `
position: fixed;
top: ${0}px;
right: ${0}px;
z-index: 9998;
width: calc(50% - 50px);
height: 100vh;
background: white;
border: 1px solid #ccc;
border-radius: ${0}px;
box-shadow: -2px 2px 10px rgba(0,0,0,0.2);
overflow-y: auto;
padding: 10px;
`;
infobox.textContent = '加载差异内容中...';
document.body.appendChild(infobox);
// 检查缓存
const cachedDiff = diffCache.get(diffLink.href);
if (cachedDiff) {
// 使用缓存 content
infobox.innerHTML = cachedDiff;
const diffTable = infobox.querySelector('table.diff');
if (diffTable) {
diffTable.style.width = '100%';
}
} else {
// 缓存未命中时加载
infobox.textContent = '加载差异中...';
iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = diffLink.href;
iframe.onload = () => {
try {
const diffTable = iframe.contentDocument.querySelector('table.diff');
if (diffTable) {
diffTable.style.width = '100%';
infobox.innerHTML = '';
infobox.appendChild(diffTable);
// 更新缓存
diffCache.set(diffLink.href, diffTable.outerHTML);
} else {
infobox.textContent = '未找到差异表格';
}
} catch (e) {
infobox.textContent = '无法加载差异内容: ' + e.message;
}
iframe.remove();
};
document.body.appendChild(iframe);
}
});
// 鼠标离开时移除预览框
linkContainer.addEventListener('mouseleave', () => {
if (infobox) infobox.remove();
if (iframe) iframe.remove();
});
});
}
})();

沪公网安备 31011002002714 号