- // ==UserScript==
- // @name WQHarvester 文泉收割机
- // @namespace http://tampermonkey.net/
- // @version 1.5
- // @description 下载文泉书局已购电子书,自动合并阅读器中的书页切片并下载为完整页面图片,需结合仓库里的另一个 Python 脚本使用。
- // @author zetaloop
- // @homepage https://github.com/zetaloop/WQHarvester
- // @match https://wqbook.wqxuetang.com/deep/read/pdf*
- // @match *://wqbook.wqxuetang.com/deep/read/pdf*
- // @grant none
- // @license The Unlicense
- // ==/UserScript==
-
- (function () {
- "use strict";
-
- console.log("文泉收割机已加载");
-
- // 跟踪每页的切片加载情况,key为页面index,值为Map {leftValue -> {img, count}}
- const pageSlices = {};
-
- // 当前处理的最小页面
- let currentMinPage = Infinity;
-
- // 起始页面
- let startPage = 1;
-
- // 已完成(合并保存)的页面集合
- const completedPages = new Set();
-
- // 待合并的页面集合(切片已加载完成但尚未合并)
- const pendingPages = new Set();
-
- // 处理中的页面集合(等待切片加载中)
- const processingPages = new Set();
-
- // 当前活动页面(用于控制只合并当前页)
- let activePage = null;
-
- // 是否正在运行
- let isRunning = false;
-
- // 脚本是否已初始化
- let isInitialized = false;
-
- // 是否有面板已创建
- let panelCreated = false;
-
- // DOM观察器引用
- let observer = null;
-
- // 页面跳转定时器(确保同时只有一个跳转等待)
- let jumpTimeout = null;
-
- // 面板各元素引用
- let mainPanel,
- statusDisplay,
- progressDisplay,
- currentPageInfo,
- mergedProgressDisplay,
- completionNotice;
-
- // 消息定时器
- let noticeTimer = null;
-
- // 自动点击“重新加载本页”按钮的定时器
- let reloadInterval = null;
-
- // 全局合并用的画布,复用以提升效率
- let mergeCanvas = null;
-
- // 提取书籍ID
- function getBookId() {
- const urlParams = new URLSearchParams(window.location.search);
- return urlParams.get("bid") || "unknown";
- }
-
- // 新增:保存目录信息为 JSON 文件({bookid}_toc.json)
- // JSON 结构:数组,每个节点包含 name(目录名称)、page(对应页码)和 children(子节点数组)
- function saveTOC() {
- // 查找目录容器(目录区域一般带有 class "catalogue left-scroll")
- const tocContainer = document.querySelector(".catalogue.left-scroll");
- if (!tocContainer) {
- console.log("未找到目录容器,跳过保存目录。");
- return;
- }
- // 在目录容器中查找目录树(一般带有 class "el-tree book-tree")
- const treeRoot = tocContainer.querySelector(".el-tree.book-tree");
- if (!treeRoot) {
- console.log("未找到目录树,跳过保存目录。");
- return;
- }
- // 递归解析目录树
- function parseTreeItems(container) {
- let items = [];
- const treeItems = container.querySelectorAll(
- ':scope > [role="treeitem"]'
- );
- treeItems.forEach((item) => {
- let obj = {};
- const node = item.querySelector(".tree-node");
- if (node) {
- const leftSpan = node.querySelector(".node-left");
- const rightSpan = node.querySelector(".node-right");
- if (leftSpan) {
- obj.name = leftSpan.textContent.trim();
- }
- if (rightSpan) {
- const pageSpan = rightSpan.querySelector("span");
- if (pageSpan) {
- obj.page = pageSpan.textContent.trim();
- } else {
- obj.page = null;
- }
- } else {
- obj.page = null;
- }
- }
- const childrenGroup = item.querySelector(
- ':scope > [role="group"]'
- );
- if (childrenGroup) {
- obj.children = parseTreeItems(childrenGroup);
- } else {
- obj.children = [];
- }
- items.push(obj);
- });
- return items;
- }
- const toc = parseTreeItems(treeRoot);
- const tocJson = JSON.stringify(toc, null, 2);
- const bookid = getBookId();
- const filename = `${bookid}_toc.json`;
- const blob = new Blob([tocJson], { type: "application/json" });
- const a = document.createElement("a");
- a.href = URL.createObjectURL(blob);
- a.download = filename;
- a.style.display = "none";
- document.body.appendChild(a);
- a.click();
- setTimeout(() => {
- URL.revokeObjectURL(a.href);
- document.body.removeChild(a);
- }, 100);
- console.log(`目录已保存为 ${filename}`);
- }
-
- // 自动检测并点击“重新加载本页”按钮(每秒检测一次)
- function checkReloadButton() {
- const reloadButtons = document.querySelectorAll(".reload_image");
- reloadButtons.forEach((btn) => {
- if (btn.offsetParent !== null) {
- // 如果元素可见
- const pageBox = btn.closest(".page-img-box");
- if (pageBox) {
- const pageIndex = pageBox.getAttribute("index");
- if (!completedPages.has(pageIndex)) {
- console.log(
- `检测到页面 ${pageIndex} 的“重新加载本页”按钮,自动点击`
- );
- updateStatusDisplay(
- `检测到页面 ${pageIndex} 重载按钮,正在点击...`
- );
- btn.click();
- }
- }
- }
- });
- }
-
- // 显示临时通知消息
- function showNotice(message, duration = 500) {
- if (!completionNotice) return;
- if (noticeTimer) clearTimeout(noticeTimer);
- completionNotice.textContent = message;
- completionNotice.style.opacity = "1";
- noticeTimer = setTimeout(() => {
- completionNotice.style.opacity = "0";
- }, duration);
- }
-
- // 更新状态信息显示
- function updateStatusDisplay(message) {
- if (statusDisplay) {
- statusDisplay.textContent = message;
- }
- }
-
- // 更新当前页面加载进度显示(针对切片加载进度,显示当前页及加载的切片数量)
- function updateCurrentPageInfo(message) {
- if (currentPageInfo) {
- currentPageInfo.innerHTML = message;
- }
- }
-
- // 更新当前页面的加载进度条(基于切片加载情况,按left区间分6块;颜色根据加载次数)
- function updateProgressBar(pageIndex, slices) {
- if (!progressDisplay) return;
- progressDisplay.innerHTML = "";
- if (!slices || slices.size === 0) {
- progressDisplay.innerHTML = `
- <div class="progress-container">
- <div class="progress-item"></div>
- <div class="progress-item"></div>
- <div class="progress-item"></div>
- <div class="progress-item"></div>
- <div class="progress-item"></div>
- <div class="progress-item"></div>
- </div>
- `;
- return;
- }
- // 获取所有切片条目,并转换left为数字,保留count信息
- const sliceEntries = Array.from(slices.entries()).map(([left, obj]) => [
- parseFloat(left),
- obj,
- ]);
- sliceEntries.sort((a, b) => a[0] - b[0]);
- const minLeft = sliceEntries[0][0];
- const maxLeft = sliceEntries[sliceEntries.length - 1][0];
- const range = maxLeft - minLeft;
- const interval = range / 5; // 分成6段
-
- const container = document.createElement("div");
- container.className = "progress-container";
-
- // 为每个区间创建一个进度块
- for (let i = 0; i < 6; i++) {
- const lowerBound =
- i === 0 ? minLeft - 0.1 : minLeft + interval * (i - 0.01);
- const upperBound =
- i === 5 ? maxLeft + 0.1 : minLeft + interval * (i + 1.01);
- const progressItem = document.createElement("div");
- progressItem.className = "progress-item";
-
- // 找到落在该区间的切片,计算最大加载次数
- const entriesInInterval = sliceEntries.filter(
- ([left, obj]) => left >= lowerBound && left <= upperBound
- );
- if (entriesInInterval.length > 0) {
- const maxCount = Math.max(
- ...entriesInInterval.map((e) => e[1].count)
- );
- // 第一次加载(count==1)使用淡绿色,否则使用深绿色
- if (maxCount === 1) {
- progressItem.classList.add("loaded-light");
- } else {
- progressItem.classList.add("loaded-dark");
- }
- }
- container.appendChild(progressItem);
- }
-
- progressDisplay.appendChild(container);
- }
-
- // 更新合并进度显示(显示已合并页数 / 总页数)
- function updateMergedProgress() {
- if (!mergedProgressDisplay) return;
- // 将 completedPages 中的所有索引统一转为字符串去重
- const mergedIndexes = new Set();
- completedPages.forEach((index) => mergedIndexes.add(String(index)));
- const totalPages = document.querySelectorAll(".page-img-box").length;
- mergedProgressDisplay.textContent = `合并进度:已合并 ${mergedIndexes.size} / ${totalPages} 页`;
- }
-
- // 获取当前视口中最“可见”的页面索引
- function getCurrentVisiblePage() {
- const pageElements = document.querySelectorAll(".page-img-box");
- if (!pageElements || pageElements.length === 0) return null;
- const windowHeight = window.innerHeight;
- const scrollTop = window.scrollY || document.documentElement.scrollTop;
- let bestVisiblePage = null,
- bestVisibility = 0;
- pageElements.forEach((page) => {
- const rect = page.getBoundingClientRect();
- const pageTop = rect.top + scrollTop;
- const pageBottom = rect.bottom + scrollTop;
- const visibleTop = Math.max(pageTop, scrollTop);
- const visibleBottom = Math.min(
- pageBottom,
- scrollTop + windowHeight
- );
- const visibleHeight = Math.max(0, visibleBottom - visibleTop);
- if (visibleHeight > bestVisibility) {
- bestVisibility = visibleHeight;
- bestVisiblePage = parseInt(page.getAttribute("index"));
- }
- });
- return bestVisiblePage;
- }
-
- // 修改后的跳转函数:滚动后立即尝试合并,500ms后检查视口位置,如未到位则重试滚动
- function jumpToPage(pageIndex, isRetry = false) {
- const pageBox = document.querySelector(
- `.page-img-box[index="${pageIndex}"]`
- );
- if (!pageBox) {
- console.log(`找不到第${pageIndex}页元素`);
- updateStatusDisplay(`找不到第${pageIndex}页元素`);
- return;
- }
- pageBox.scrollIntoView({ behavior: "smooth", block: "end" });
- console.log(`正在跳转第${pageIndex}页${isRetry ? "(重试)" : ""}`);
- updateStatusDisplay(`正在跳转第${pageIndex}页...`);
-
- // 立即检查:如果该页已标记为待合并,则立刻开始合并
- if (pendingPages.has(pageIndex.toString()) && isRunning) {
- console.log(`当前活动页面${pageIndex}切片已加载,立即开始合并...`);
- mergeAndSavePage(getBookId(), pageIndex.toString());
- }
-
- // 500ms后检查当前视口是否正确,如有偏差则重试滚动,并强制调用页面完成检测
- if (jumpTimeout) clearTimeout(jumpTimeout);
- jumpTimeout = setTimeout(() => {
- jumpTimeout = null;
- const currentPage = getCurrentVisiblePage();
- console.log(`跳转后检测: 目标=${pageIndex}, 当前=${currentPage}`);
- if (
- currentPage !== null &&
- Math.abs(currentPage - pageIndex) > 2 &&
- !isRetry
- ) {
- console.log(`跳转偏差过大,再次尝试跳转到第${pageIndex}页`);
- jumpToPage(pageIndex, true);
- } else {
- updateStatusDisplay(`正在转到第${pageIndex}页...`);
- activePage = pageIndex;
- if (pageSlices[pageIndex]) {
- updateProgressBar(pageIndex, pageSlices[pageIndex]);
- updateCurrentPageInfo(
- `当前页面:<b>第${pageIndex}页</b> (加载切片 ${pageSlices[pageIndex].size} 个)`
- );
- } else {
- updateProgressBar(pageIndex, null);
- updateCurrentPageInfo(
- `当前页面:<b>第${pageIndex}页</b> (尚未加载切片)`
- );
- }
- // 强制再次检查页面是否已完成加载
- checkPageCompletion(getBookId(), pageIndex);
- }
- }, 500);
- }
-
- // 处理并记录单个切片图片(同一 left 值如果重复,则累加 count)
- function processSliceImage(imgElement, bookId, pageIndex, leftValue) {
- if (!isRunning || parseInt(pageIndex) < startPage) return;
- if (!pageSlices[pageIndex]) {
- pageSlices[pageIndex] = new Map();
- processingPages.add(pageIndex);
- }
- if (pageSlices[pageIndex].has(leftValue)) {
- // 重复加载,累加计数
- let entry = pageSlices[pageIndex].get(leftValue);
- entry.count++;
- pageSlices[pageIndex].set(leftValue, entry);
- } else {
- pageSlices[pageIndex].set(leftValue, { img: imgElement, count: 1 });
- }
- if (activePage == pageIndex) {
- updateProgressBar(pageIndex, pageSlices[pageIndex]);
- updateCurrentPageInfo(
- `当前页面:<b>第${pageIndex}页</b> (加载切片 ${pageSlices[pageIndex].size} 个)`
- );
- }
- if (
- parseInt(pageIndex) < currentMinPage &&
- !completedPages.has(pageIndex)
- ) {
- currentMinPage = parseInt(pageIndex);
- jumpToPage(currentMinPage);
- }
- checkPageCompletion(bookId, pageIndex);
- }
-
- // 检查页面切片是否全部加载完成
- function checkPageCompletion(bookId, pageIndex) {
- const pageBox = document.querySelector(
- `.page-img-box[index="${pageIndex}"]`
- );
- if (!pageBox) return;
- const plgContainer = pageBox.querySelector(".plg");
- if (!plgContainer) return;
- const totalSlices = plgContainer.querySelectorAll("img").length;
- const currentSlices = pageSlices[pageIndex]
- ? pageSlices[pageIndex].size
- : 0;
- if (
- totalSlices > 0 &&
- currentSlices >= totalSlices &&
- !completedPages.has(pageIndex) &&
- !pendingPages.has(pageIndex)
- ) {
- console.log(`第${pageIndex}页的所有切片已加载,标记为待合并`);
- pendingPages.add(pageIndex);
- if (activePage == pageIndex && isRunning) {
- console.log(`当前活动页面${pageIndex}切片已加载,开始合并...`);
- mergeAndSavePage(bookId, pageIndex);
- }
- }
- }
-
- // 合并切片并保存为完整页面(保存文件名不带 _complete)——优化点:复用全局画布,优先使用 OffscreenCanvas
- function mergeAndSavePage(bookId, pageIndex) {
- if (
- !pageSlices[pageIndex] ||
- pageSlices[pageIndex].size === 0 ||
- completedPages.has(pageIndex) ||
- !isRunning
- )
- return;
- pendingPages.delete(pageIndex);
- updateStatusDisplay(`正在合并第${pageIndex}页...`);
- try {
- const sortedSlices = Array.from(
- pageSlices[pageIndex].entries()
- ).sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
- let totalWidth = 0,
- maxHeight = 0;
- sortedSlices.forEach(([left, entry]) => {
- totalWidth += entry.img.naturalWidth;
- maxHeight = Math.max(maxHeight, entry.img.naturalHeight);
- });
- // 初始化或复用全局画布
- if (!mergeCanvas) {
- if (typeof OffscreenCanvas !== "undefined") {
- mergeCanvas = new OffscreenCanvas(totalWidth, maxHeight);
- } else {
- mergeCanvas = document.createElement("canvas");
- }
- }
- mergeCanvas.width = totalWidth;
- mergeCanvas.height = maxHeight;
- const ctx = mergeCanvas.getContext("2d");
- let currentX = 0;
- sortedSlices.forEach(([left, entry]) => {
- ctx.drawImage(entry.img, currentX, 0);
- currentX += entry.img.naturalWidth;
- });
- // 文件名格式:{bookid}_page{pageIndex}.webp
- const filename = `${bookId}_page${pageIndex}.webp`;
- // 如果使用 OffscreenCanvas 优先使用 convertToBlob
- if (
- mergeCanvas instanceof OffscreenCanvas &&
- mergeCanvas.convertToBlob
- ) {
- mergeCanvas
- .convertToBlob({ type: "image/webp", quality: 0.95 })
- .then((blob) => {
- saveBlob(blob, filename, pageIndex);
- });
- } else {
- mergeCanvas.toBlob(
- function (blob) {
- saveBlob(blob, filename, pageIndex);
- },
- "image/webp",
- 0.95
- );
- }
- } catch (error) {
- console.error(`合并第${pageIndex}页失败:`, error);
- updateStatusDisplay(`合并第${pageIndex}页时出错:${error.message}`);
- }
- }
- // 将生成的 Blob 保存为下载文件
- function saveBlob(blob, filename, pageIndex) {
- if (!saveBlob.savedFiles) {
- saveBlob.savedFiles = new Set();
- }
- if (saveBlob.savedFiles.has(filename)) {
- console.log(`文件 ${filename} 已经保存,跳过重复保存`);
- setTimeout(() => {
- completedPages.add(pageIndex);
- processingPages.delete(pageIndex);
- showNotice(`✓ 第${pageIndex}页已保存为 ${filename}`);
- updateStatusDisplay(`合并完成,继续处理...`);
- updateMergedProgress();
- console.log("查找下一个未合并页面...");
- findAndJumpToNextPage();
- }, 100);
- return;
- }
- saveBlob.savedFiles.add(filename);
- const link = document.createElement("a");
- link.href = URL.createObjectURL(blob);
- link.download = filename;
- link.style.display = "none";
- document.body.appendChild(link);
- link.click();
- setTimeout(() => {
- URL.revokeObjectURL(link.href);
- document.body.removeChild(link);
- console.log(`已保存合并页面:${filename}`);
- completedPages.add(pageIndex);
- processingPages.delete(pageIndex);
- showNotice(`✓ 第${pageIndex}页已保存为 ${filename}`);
- updateStatusDisplay(`合并完成,继续处理...`);
- updateMergedProgress();
- console.log("查找下一个未合并页面...");
- findAndJumpToNextPage();
- }, 100);
- }
-
- // 查找并跳转到下一个未合并页面
- function findAndJumpToNextPage() {
- if (!isRunning) return;
- console.log("查找下一个未合并页面...");
- const allPages = document.querySelectorAll(".page-img-box");
- const allPageIndices = [];
- allPages.forEach((page) => {
- const idx = parseInt(page.getAttribute("index"));
- if (idx >= startPage) allPageIndices.push(idx);
- });
- allPageIndices.sort((a, b) => a - b);
- let nextPage = null;
- for (let i = 0; i < allPageIndices.length; i++) {
- const pageIdx = allPageIndices[i].toString();
- if (
- !completedPages.has(pageIdx) &&
- parseInt(pageIdx) >= startPage
- ) {
- nextPage = parseInt(pageIdx);
- break;
- }
- }
- if (nextPage !== null) {
- currentMinPage = nextPage;
- console.log(`跳转到下一未合并页面:${nextPage}`);
- jumpToPage(nextPage);
- } else {
- updateStatusDisplay(`所有页面处理完成!`);
- showNotice(`✓ 所有页面处理完成!`, 2000);
- const cancelButton = document.getElementById("cancelButton");
- const startButton = document.getElementById("startButton");
- if (cancelButton) cancelButton.style.display = "none";
- if (startButton) {
- startButton.disabled = false;
- startButton.textContent = "重新开始";
- startButton.style.backgroundColor = "#4CAF50";
- startButton.style.display = "block";
- }
- isRunning = false;
- }
- }
-
- // 处理页面中已有的图片
- function processExistingImages() {
- if (!isRunning) return;
- const bookId = getBookId();
- console.log(`检测到书籍ID:${bookId}`);
- document.querySelectorAll(".page-img-box").forEach((pageBox) => {
- const pageIndex = pageBox.getAttribute("index");
- if (parseInt(pageIndex) < startPage) return;
- const plgContainer = pageBox.querySelector(".plg");
- if (!plgContainer) return;
- const sliceImages = plgContainer.querySelectorAll("img");
- sliceImages.forEach((img) => {
- if (img.complete && img.naturalHeight !== 0) {
- const leftValue = parseFloat(img.style.left) || 0;
- processSliceImage(img, bookId, pageIndex, leftValue);
- } else {
- img.addEventListener("load", function () {
- if (!isRunning) return;
- const leftValue = parseFloat(img.style.left) || 0;
- processSliceImage(img, bookId, pageIndex, leftValue);
- });
- }
- });
- });
- }
-
- // 设置DOM观察器监控新添加的图片
- function setupObserver() {
- if (observer) {
- observer.disconnect();
- }
- const bookId = getBookId();
- observer = new MutationObserver((mutations) => {
- if (!isRunning) return;
- mutations.forEach((mutation) => {
- if (mutation.addedNodes.length) {
- mutation.addedNodes.forEach((node) => {
- if (
- node.nodeName === "IMG" &&
- node.parentElement &&
- node.parentElement.classList.contains("plg")
- ) {
- const pageBox = node.closest(".page-img-box");
- if (pageBox) {
- const pageIndex = pageBox.getAttribute("index");
- if (parseInt(pageIndex) < startPage) return;
- if (node.complete && node.naturalHeight !== 0) {
- const leftValue =
- parseFloat(node.style.left) || 0;
- processSliceImage(
- node,
- bookId,
- pageIndex,
- leftValue
- );
- } else {
- node.addEventListener("load", function () {
- if (!isRunning) return;
- const leftValue =
- parseFloat(node.style.left) || 0;
- processSliceImage(
- node,
- bookId,
- pageIndex,
- leftValue
- );
- });
- }
- }
- }
- });
- }
- });
- });
- const config = {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ["src", "style"],
- };
- observer.observe(document.body, config);
- }
-
- // 停止所有处理
- function stopProcessing() {
- isRunning = false;
- if (observer) {
- observer.disconnect();
- observer = null;
- }
- updateStatusDisplay("已停止处理");
- showNotice("已取消处理", 1000);
- const startButton = document.getElementById("startButton");
- const cancelButton = document.getElementById("cancelButton");
- if (startButton) {
- startButton.disabled = false;
- startButton.textContent = "重新开始";
- startButton.style.backgroundColor = "#4CAF50";
- startButton.style.display = "block";
- }
- if (cancelButton) {
- cancelButton.style.display = "none";
- }
- if (reloadInterval) {
- clearInterval(reloadInterval);
- reloadInterval = null;
- }
- }
-
- // 添加增强的交互界面(包括进度显示、按钮、以及自动点击重载按钮)
- function addEnhancedUI() {
- if (panelCreated) return;
- const style = document.createElement("style");
- style.textContent = `
- #wqSlicerPanel {
- position: fixed;
- top: 100px;
- right: 10px;
- background-color: rgba(255,255,255,0.97);
- color: #333;
- padding: 12px;
- border-radius: 8px;
- z-index: 9999;
- width: 300px;
- font-family: Arial, sans-serif;
- box-shadow: 0 0 15px rgba(0,0,0,0.3);
- transition: all 0.3s ease;
- }
- #wqSlicerPanel .panel-header {
- font-weight: bold;
- margin-bottom: 12px;
- font-size: 15px;
- border-bottom: 1px solid #ddd;
- padding-bottom: 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- #wqSlicerPanel .panel-section {
- margin-bottom: 12px;
- padding-bottom: 8px;
- border-bottom: 1px solid #eee;
- }
- #wqSlicerPanel .buttons-container {
- display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
- }
- #wqSlicerPanel button {
- padding: 6px 12px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-weight: bold;
- transition: all 0.2s;
- }
- #wqSlicerPanel button:hover { opacity: 0.9; }
- #wqSlicerPanel button:active { transform: scale(0.98); }
- #startButton { background: #4CAF50; color: white; flex-grow: 1; }
- #cancelButton { background: #f44336; color: white; flex-grow: 1; display: none; }
- #currentPageInfo { font-size: 13px; margin-bottom: 10px; color: #333; }
- #progressDisplay { margin: 10px 0; }
- .progress-container {
- display: flex;
- justify-content: space-between;
- height: 12px;
- margin: 5px 0;
- background: #f0f0f0;
- border-radius: 6px;
- overflow: hidden;
- }
- .progress-item {
- flex-grow: 1;
- height: 100%;
- background: #f0f0f0;
- margin: 0 1px;
- transition: all 0.3s ease;
- }
- .progress-item.loaded-light { background: #a8d5a2; }
- .progress-item.loaded-dark { background: #4CAF50; }
- #statusDisplay, #mergedProgressDisplay, #completionNotice { font-size: 13px; color: #555; min-height: 20px; }
- #mergedProgressDisplay { margin-top: 5px; }
- #completionNotice { color: #4CAF50; margin-top: 8px; font-weight: bold; opacity: 0; transition: opacity 0.5s ease; }
- `;
- document.head.appendChild(style);
-
- const oldPanel = document.getElementById("wqSlicerPanel");
- if (oldPanel) oldPanel.remove();
-
- const panel = document.createElement("div");
- panel.id = "wqSlicerPanel";
- panel.innerHTML = `
- <div class="panel-header">
- <span>文泉收割机</span>
- </div>
- <div class="panel-section">
- <div class="buttons-container">
- <button id="startButton">开始处理</button>
- <button id="cancelButton">取消处理</button>
- </div>
- </div>
- <div class="panel-section">
- <div id="currentPageInfo">当前页面:等待开始...</div>
- <div id="progressDisplay"></div>
- </div>
- <div class="panel-section">
- <div id="mergedProgressDisplay">合并进度:0 页</div>
- <div id="statusDisplay">点击“开始处理”启动工具</div>
- <div id="completionNotice"></div>
- </div>
- `;
- document.body.appendChild(panel);
- panelCreated = true;
-
- mainPanel = panel;
- statusDisplay = document.getElementById("statusDisplay");
- progressDisplay = document.getElementById("progressDisplay");
- currentPageInfo = document.getElementById("currentPageInfo");
- mergedProgressDisplay = document.getElementById(
- "mergedProgressDisplay"
- );
- completionNotice = document.getElementById("completionNotice");
-
- const startButton = document.getElementById("startButton");
- startButton.addEventListener("click", function () {
- // 首先尝试点击目录区域的 <small> 标签展开全部目录
- const expandSmall = document.querySelector(
- ".catalogue.left-scroll small"
- );
- if (expandSmall) {
- expandSmall.click();
- console.log("点击展开全部目录");
- }
- // 等待100ms后保存目录
- setTimeout(() => {
- saveTOC();
- showNotice("✓ 目录已保存");
- // 再等待100ms后继续原有流程
- setTimeout(() => {
- if (!isInitialized || !isRunning) {
- startButton.disabled = true;
- startButton.textContent = "处理中...";
- startButton.style.backgroundColor = "#888";
- startButton.style.display = "none";
- const cancelButton =
- document.getElementById("cancelButton");
- if (cancelButton) cancelButton.style.display = "block";
- if (isInitialized) {
- isRunning = true;
- initScript(false);
- } else {
- isRunning = true;
- initScript(true);
- }
- // 启动自动点击重载按钮的检测,每秒执行一次
- if (!reloadInterval) {
- reloadInterval = setInterval(
- checkReloadButton,
- 1000
- );
- }
- }
- }, 100);
- }, 100);
- });
-
- document
- .getElementById("cancelButton")
- .addEventListener("click", function () {
- stopProcessing();
- });
-
- updateProgressBar(null, null);
- updateMergedProgress();
- }
-
- // 初始化脚本,询问起始页面
- function initScript(isFirstTime = true) {
- if (isFirstTime) {
- currentMinPage = Infinity;
- pendingPages.clear();
- processingPages.clear();
- activePage = null;
- const userStartPage = prompt("请输入要开始处理的页码:");
- // 如果用户取消或未输入页码,则取消操作并恢复开始按钮状态
- if (!userStartPage) {
- stopProcessing();
- const startButton = document.getElementById("startButton");
- if (startButton) {
- startButton.disabled = false;
- startButton.textContent = "开始处理";
- startButton.style.backgroundColor = "#4CAF50";
- startButton.style.display = "block";
- }
- updateStatusDisplay("操作已取消");
- return;
- }
- // 如果输入的内容不是有效数字,则同样取消操作
- if (isNaN(parseInt(userStartPage))) {
- stopProcessing();
- const startButton = document.getElementById("startButton");
- if (startButton) {
- startButton.disabled = false;
- startButton.textContent = "开始处理";
- startButton.style.backgroundColor = "#4CAF50";
- startButton.style.display = "block";
- }
- updateStatusDisplay("无效的页码,操作已取消");
- return;
- }
- startPage = parseInt(userStartPage);
- currentMinPage = startPage;
- jumpToPage(currentMinPage);
- console.log(`开始处理,起始页为:${startPage}`);
- updateStatusDisplay(`开始处理,起始页:第${startPage}页`);
- } else {
- findAndJumpToNextPage();
- }
- processExistingImages();
- setupObserver();
- isInitialized = true;
- }
-
- // 页面加载完成后执行
- window.addEventListener("load", function () {
- console.log("页面已加载,添加交互界面");
- addEnhancedUI();
- });
-
- // 尝试立即添加交互界面
- setTimeout(addEnhancedUI, 500);
- })();