// ==UserScript== // @name 百度网盘批量选择文件助手(悬浮球版) // @namespace http://tampermonkey.net/ // @version 8.0 // @description 支持手动输入和读取上次选择位置,UI可拖动、可隐藏,带悬浮球 // @author Assistant // @match https://pan.baidu.com/disk/* // @match https://pan.baidu.com/s/* // @match https://pan.baidu.com/share/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // ==/UserScript== (function() { 'use strict'; // 全局状态 let isSelecting = false; let autoSelectTimer = null; let targetCount = 500; let batchSize = 100; let isSharePage = false; let lastSelectedIndex = 0; let currentUrlKey = ''; // UI拖动相关变量 let isDragging = false; let isDraggingFloat = false; let dragStartX = 0; let dragStartY = 0; let panelLeft = 0; let panelTop = 0; let floatLeft = 0; let floatTop = 0; let isPanelVisible = true; // 日志面板 let logPanel = null; let logContent = []; let floatBall = null; // 获取当前页面的唯一标识 function getPageKey() { const url = window.location.href; const shareMatch = url.match(/\/s\/([a-zA-Z0-9]+)/); if (shareMatch) { return `share_${shareMatch[1]}`; } const pathMatch = url.match(/\/disk\/(.+)/); if (pathMatch) { return `disk_${pathMatch[1]}`; } return `page_${url.replace(/[^a-zA-Z0-9]/g, '_')}`; } // 保存UI位置 function saveUIPosition(left, top) { GM_setValue('ui_left', left); GM_setValue('ui_top', top); } // 保存悬浮球位置 function saveFloatPosition(left, top) { GM_setValue('float_left', left); GM_setValue('float_top', top); } // 保存UI隐藏状态 function saveUIHidden(hidden) { GM_setValue('ui_hidden', hidden); } // 获取UI位置 function getUIPosition() { const left = GM_getValue('ui_left', null); const top = GM_getValue('ui_top', null); return { left, top }; } // 获取悬浮球位置 function getFloatPosition() { const left = GM_getValue('float_left', null); const top = GM_getValue('float_top', null); return { left, top }; } // 获取UI隐藏状态 function getUIHidden() { return GM_getValue('ui_hidden', false); } // 保存上次选择的位置 function saveLastPosition(index) { const key = `lastPos_${currentUrlKey}`; GM_setValue(key, index); addLog(`💾 已保存选择位置: 第 ${index} 个文件`, 'info'); const positionInput = document.getElementById('position-input'); if (positionInput) { positionInput.value = index; } } // 获取上次选择的位置 function getLastPosition() { const key = `lastPos_${currentUrlKey}`; const pos = GM_getValue(key, 0); return pos; } // 手动设置选择位置 function setManualPosition(position) { const totalFiles = getFileRows().length; if (position < 0) { addLog(`❌ 位置不能为负数`, 'error'); return false; } if (position > totalFiles && totalFiles > 0) { addLog(`⚠ 位置 ${position} 超出文件总数 ${totalFiles},将设置为最后一个文件`, 'warning'); position = totalFiles; } lastSelectedIndex = position; saveLastPosition(lastSelectedIndex); addLog(`📌 手动设置选择位置为: 第 ${lastSelectedIndex} 个文件`, 'success'); refreshSelectedDisplay(); return true; } // 清除保存的位置 function clearLastPosition() { const key = `lastPos_${currentUrlKey}`; GM_deleteValue(key); lastSelectedIndex = 0; const positionInput = document.getElementById('position-input'); if (positionInput) { positionInput.value = ''; } addLog(`🗑 已清除保存的选择位置`, 'info'); refreshSelectedDisplay(); } // 读取保存的位置到输入框 function loadPositionToInput() { const savedPos = getLastPosition(); const positionInput = document.getElementById('position-input'); if (positionInput) { if (savedPos > 0) { positionInput.value = savedPos; addLog(`📖 已读取保存位置: 第 ${savedPos} 个文件`, 'info'); } else { positionInput.value = ''; addLog(`📖 未找到保存的位置`, 'info'); } } return savedPos; } // 添加日志 function addLog(message, type = 'info') { const timestamp = new Date().toLocaleTimeString(); const logEntry = `[${timestamp}] ${message}`; logContent.unshift(logEntry); if (logContent.length > 50) logContent.pop(); if (logPanel) { const logTextarea = logPanel.querySelector('#log-textarea'); if (logTextarea) { logTextarea.value = logContent.join('\n'); logTextarea.scrollTop = 0; } } console.log(logEntry); } // 判断是否为分享页面 function checkIsSharePage() { const url = window.location.href; isSharePage = url.includes('/s/') || url.includes('/share/'); currentUrlKey = getPageKey(); return isSharePage; } // 获取所有文件行 function getFileRows() { if (isSharePage) { return Array.from(document.querySelectorAll('dd.AuPKyz, dd.g-clearfix.AuPKyz')); } else { return Array.from(document.querySelectorAll('li.fufHyA.yfHIsP')); } } // 检查文件行是否已选中 function isRowSelected(row) { if (isSharePage) { return row.classList.contains('JS-item-active'); } else { const checkbox = row.querySelector('.Qxyfvg .NbKJexb'); return checkbox && checkbox.classList.contains('icon-checksmall'); } } // 点击选择/取消选择文件行 function clickRow(row) { if (isSharePage) { const clickable = row.querySelector('.file-name') || row; clickable.click(); return true; } else { const clickArea = row.querySelector('.Qxyfvg'); if (clickArea) { clickArea.click(); return true; } } return false; } // 选择/取消选择文件行 function setRowSelected(row, selected) { const isSelected = isRowSelected(row); if (selected && !isSelected) { return clickRow(row); } else if (!selected && isSelected) { return clickRow(row); } return false; } // 获取当前已选中的文件数量 function getSelectedCount() { if (isSharePage) { const rows = getFileRows(); let count = 0; rows.forEach(row => { if (row.classList.contains('JS-item-active')) { count++; } }); return count; } else { const selectedTextElem = document.querySelector('.MdLxwM'); if (selectedTextElem) { const match = selectedTextElem.innerText.match(/已选中(\d+)个/); if (match) { return parseInt(match[1], 10); } } const fileItems = document.querySelectorAll('li.fufHyA.yfHIsP'); let count = 0; fileItems.forEach(item => { const checkbox = item.querySelector('.Qxyfvg .NbKJexb'); if (checkbox && checkbox.classList.contains('icon-checksmall')) { count++; } }); return count; } } // 从指定位置开始选择指定数量的文件 function selectBatchFilesFromPosition(startIndex, count) { return new Promise((resolve) => { const rows = getFileRows(); if (rows.length === 0) { addLog('未找到文件列表', 'error'); resolve({ selected: 0, lastIndex: startIndex }); return; } let actualStart = startIndex; if (actualStart >= rows.length) { addLog(`⚠ 起始位置 ${startIndex + 1} 超出范围,重置为第1个`, 'warning'); actualStart = 0; } addLog(`共找到 ${rows.length} 个文件,从第 ${actualStart + 1} 个开始选择`); let selected = 0; let skipped = 0; let lastSelectedIdx = actualStart; for (let i = actualStart; i < rows.length && selected < count; i++) { if (!isRowSelected(rows[i])) { setRowSelected(rows[i], true); selected++; lastSelectedIdx = i; if (selected % 5 === 0) { const syncWait = Date.now() + 15; while (Date.now() < syncWait); } } else { skipped++; lastSelectedIdx = i; } } addLog(`本次选择了 ${selected} 个文件(跳过已选 ${skipped} 个),最后位置: ${lastSelectedIdx + 1}`); setTimeout(() => { const finalCount = getSelectedCount(); addLog(`当前总计已选中: ${finalCount} 个`); resolve({ selected: selected, lastIndex: lastSelectedIdx }); }, 500); }); } // 清除所有选中并重置位置记录 function clearAllSelection() { addLog('开始清除所有选中...'); const rows = getFileRows(); let clearedCount = 0; rows.forEach(row => { if (isRowSelected(row)) { setRowSelected(row, false); clearedCount++; if (clearedCount % 10 === 0) { const syncWait = Date.now() + 10; while (Date.now() < syncWait); } } }); clearLastPosition(); setTimeout(() => { const finalCount = getSelectedCount(); addLog(`清除完成,清除了 ${clearedCount} 个,当前已选中 ${finalCount} 个`); refreshSelectedDisplay(); }, 500); } // 重置选择位置 function resetSelectionPosition() { clearLastPosition(); addLog(`🔄 已重置选择位置,下次将从第1个文件开始选择`, 'success'); refreshSelectedDisplay(); } // 自动选择直到达到目标数量 async function autoSelectUntilTarget() { if (!isSelecting) { addLog('自动选择已停止', 'warning'); return; } const rows = getFileRows(); if (rows.length === 0) { addLog('未找到文件列表,请确保页面已加载完成', 'error'); stopAutoSelect(); return; } const currentSelected = getSelectedCount(); updateStatusDisplay(currentSelected, targetCount); if (currentSelected >= targetCount) { addLog(`✓ 已达到目标数量 ${targetCount} 个,自动选择完成!`, 'success'); stopAutoSelect(); return; } let startPos = lastSelectedIndex; if (startPos >= rows.length) { addLog(`⚠ 上次位置 ${startPos + 1} 超出范围,重置为第1个`, 'warning'); startPos = 0; lastSelectedIndex = 0; saveLastPosition(0); } const needSelect = targetCount - currentSelected; const toSelect = Math.min(batchSize, needSelect); addLog(`当前已选 ${currentSelected}/${targetCount},从第 ${startPos + 1} 个开始选择 ${toSelect} 个...`); const result = await selectBatchFilesFromPosition(startPos, toSelect); if (result.selected > 0) { lastSelectedIndex = result.lastIndex + 1; saveLastPosition(lastSelectedIndex); const newSelected = getSelectedCount(); if (newSelected > currentSelected && isSelecting) { autoSelectTimer = setTimeout(() => { autoSelectUntilTarget(); }, 800); } else if (newSelected < targetCount && result.lastIndex >= rows.length - 1) { addLog(`⚠ 已到达文件列表末尾,当前已选 ${newSelected} 个,未达到目标 ${targetCount} 个`, 'warning'); stopAutoSelect(); } else if (isSelecting) { autoSelectTimer = setTimeout(() => { autoSelectUntilTarget(); }, 800); } } else { if (result.lastIndex >= rows.length - 1) { addLog(`⚠ 已到达文件列表末尾,无法继续选择`, 'warning'); stopAutoSelect(); } else { addLog(`⚠ 未选择到新文件,可能已全部选中`, 'warning'); stopAutoSelect(); } } } // 开始自动选择 function startAutoSelect() { if (isSelecting) { addLog('已在运行中', 'warning'); return; } const targetInput = document.getElementById('target-count-input'); if (targetInput) { const val = parseInt(targetInput.value, 10); if (!isNaN(val) && val > 0) { targetCount = val; } } const batchInput = document.getElementById('batch-size-input'); if (batchInput) { const val = parseInt(batchInput.value, 10); if (!isNaN(val) && val > 0 && val <= 500) { batchSize = val; } } const positionInput = document.getElementById('position-input'); if (positionInput && positionInput.value) { const manualPos = parseInt(positionInput.value, 10); if (!isNaN(manualPos) && manualPos >= 0) { const totalFiles = getFileRows().length; if (manualPos <= totalFiles || totalFiles === 0) { lastSelectedIndex = manualPos; saveLastPosition(lastSelectedIndex); addLog(`📌 使用手动设置的位置: 第 ${lastSelectedIndex} 个文件`, 'info'); } else { addLog(`⚠ 手动位置 ${manualPos} 超出文件总数 ${totalFiles},使用保存的位置`, 'warning'); } } } addLog(`========== 开始自动选择 ==========`); addLog(`目标数量: ${targetCount} 个,每批次: ${batchSize} 个`); const currentSelected = getSelectedCount(); addLog(`当前已选中: ${currentSelected} 个`); const totalFiles = getFileRows().length; addLog(`文件夹内共有: ${totalFiles} 个文件`); const startPos = lastSelectedIndex; if (startPos > 0) { addLog(`📌 将从第 ${startPos + 1} 个文件开始选择`, 'info'); } else { addLog(`📌 将从第 1 个文件开始选择`, 'info'); } if (currentSelected >= targetCount) { addLog(`当前已选中 ${currentSelected} 个,已达到目标 ${targetCount},无需选择`, 'success'); return; } if (totalFiles === 0) { addLog(`未检测到文件,请刷新页面重试`, 'error'); return; } isSelecting = true; updateUIState(true); autoSelectUntilTarget(); } // 停止自动选择 function stopAutoSelect() { if (autoSelectTimer) { clearTimeout(autoSelectTimer); autoSelectTimer = null; } isSelecting = false; updateUIState(false); addLog('自动选择已停止,位置已保存,下次可继续', 'warning'); } // 更新UI按钮状态 function updateUIState(isRunning) { const buttons = [ 'start-select-btn', 'stop-select-btn', 'target-count-input', 'batch-size-input', 'position-input', 'set-position-btn', 'load-position-btn', 'clear-select-btn', 'reset-position-btn' ]; buttons.forEach(id => { const el = document.getElementById(id); if (el) { if (id === 'stop-select-btn') { el.disabled = !isRunning; } else if (id === 'start-select-btn') { el.disabled = isRunning; } else { el.disabled = isRunning; } } }); } // 更新状态显示 function updateStatusDisplay(current, target) { const statusSpan = document.getElementById('select-status'); const progressSpan = document.getElementById('select-progress'); const currentSpan = document.getElementById('current-selected'); const positionSpan = document.getElementById('last-position-display'); if (statusSpan) { statusSpan.textContent = isSelecting ? '运行中' : '空闲'; statusSpan.style.backgroundColor = isSelecting ? '#5cb85c' : 'rgba(255,255,255,0.2)'; } if (progressSpan) { progressSpan.textContent = `${current} / ${target}`; } if (currentSpan) { currentSpan.textContent = current; currentSpan.style.color = current >= target ? '#5cb85c' : '#3c8dbc'; } if (positionSpan) { const savedPos = getLastPosition(); if (savedPos > 0) { positionSpan.textContent = `第 ${savedPos} 个`; positionSpan.style.color = '#f0ad4e'; } else { positionSpan.textContent = '未设置'; positionSpan.style.color = '#999'; } } const totalFiles = getFileRows().length; const totalSpan = document.getElementById('total-files'); if (totalSpan) { totalSpan.textContent = totalFiles; } // 更新悬浮球上的数字 if (floatBall) { const countSpan = floatBall.querySelector('.float-count'); if (countSpan) { countSpan.textContent = current; } if (isSelecting) { floatBall.style.backgroundColor = '#5cb85c'; } else { floatBall.style.backgroundColor = '#3c8dbc'; } } } // 刷新当前选中数量显示 function refreshSelectedDisplay() { const count = getSelectedCount(); updateStatusDisplay(count, targetCount); } // 切换面板显示/隐藏 function togglePanel() { const panel = document.getElementById('batch-select-panel-v8'); if (panel) { if (isPanelVisible) { panel.style.display = 'none'; isPanelVisible = false; saveUIHidden(true); if (floatBall) { floatBall.style.display = 'flex'; } addLog('面板已隐藏,可通过悬浮球重新显示', 'info'); } else { panel.style.display = 'block'; isPanelVisible = true; saveUIHidden(false); if (floatBall) { floatBall.style.display = 'none'; } addLog('面板已显示', 'info'); } } } // 显示面板 function showPanel() { const panel = document.getElementById('batch-select-panel-v8'); if (panel && !isPanelVisible) { panel.style.display = 'block'; isPanelVisible = true; saveUIHidden(false); if (floatBall) { floatBall.style.display = 'none'; } addLog('面板已显示', 'info'); } } // 创建悬浮球 function createFloatBall() { if (floatBall) return; floatBall = document.createElement('div'); floatBall.id = 'float-ball'; const { left, top } = getFloatPosition(); let positionStyle = ''; if (left !== null && top !== null) { positionStyle = `left: ${left}px; top: ${top}px; right: auto; bottom: auto;`; } else { positionStyle = `right: 20px; bottom: 80px;`; } floatBall.style.cssText = ` position: fixed; ${positionStyle} width: 56px; height: 56px; background: #3c8dbc; border-radius: 50%; box-shadow: 0 2px 10px rgba(0,0,0,0.3); cursor: move; z-index: 10001; display: ${isPanelVisible ? 'none' : 'flex'}; flex-direction: column; align-items: center; justify-content: center; color: white; font-family: "Microsoft YaHei", Arial, sans-serif; transition: transform 0.2s; `; floatBall.innerHTML = `
📂
0
`; // 悬浮球点击事件 floatBall.addEventListener('click', (e) => { if (!isDraggingFloat) { showPanel(); } }); // 悬浮球拖动 let floatDragStartX = 0, floatDragStartY = 0; floatBall.addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); isDraggingFloat = true; const rect = floatBall.getBoundingClientRect(); floatDragStartX = e.clientX - rect.left; floatDragStartY = e.clientY - rect.top; floatBall.style.transition = 'none'; document.addEventListener('mousemove', onFloatDrag); document.addEventListener('mouseup', stopFloatDrag); }); function onFloatDrag(e) { if (!isDraggingFloat) return; let newLeft = e.clientX - floatDragStartX; let newTop = e.clientY - floatDragStartY; // 边界限制 const maxX = window.innerWidth - floatBall.offsetWidth; const maxY = window.innerHeight - floatBall.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxX)); newTop = Math.max(0, Math.min(newTop, maxY)); floatBall.style.left = newLeft + 'px'; floatBall.style.top = newTop + 'px'; floatBall.style.right = 'auto'; floatBall.style.bottom = 'auto'; } function stopFloatDrag() { if (!isDraggingFloat) return; isDraggingFloat = false; floatBall.style.transition = ''; const left = parseFloat(floatBall.style.left); const top = parseFloat(floatBall.style.top); if (!isNaN(left) && !isNaN(top)) { saveFloatPosition(left, top); } document.removeEventListener('mousemove', onFloatDrag); document.removeEventListener('mouseup', stopFloatDrag); } document.body.appendChild(floatBall); } // 面板拖动 function startDrag(e) { const panel = document.getElementById('batch-select-panel-v8'); if (!panel) return; const header = e.target.closest('.panel-header'); if (!header) return; e.preventDefault(); isDragging = true; const rect = panel.getBoundingClientRect(); dragStartX = e.clientX - rect.left; dragStartY = e.clientY - rect.top; panel.style.position = 'fixed'; panel.style.left = rect.left + 'px'; panel.style.top = rect.top + 'px'; panel.style.right = 'auto'; panel.style.bottom = 'auto'; document.addEventListener('mousemove', onPanelDrag); document.addEventListener('mouseup', stopPanelDrag); panel.style.cursor = 'move'; } function onPanelDrag(e) { if (!isDragging) return; const panel = document.getElementById('batch-select-panel-v8'); if (!panel) return; let newLeft = e.clientX - dragStartX; let newTop = e.clientY - dragStartY; const maxX = window.innerWidth - panel.offsetWidth; const maxY = window.innerHeight - panel.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxX)); newTop = Math.max(0, Math.min(newTop, maxY)); panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px'; saveUIPosition(newLeft, newTop); } function stopPanelDrag() { if (!isDragging) return; isDragging = false; const panel = document.getElementById('batch-select-panel-v8'); if (panel) { panel.style.cursor = ''; } document.removeEventListener('mousemove', onPanelDrag); document.removeEventListener('mouseup', stopPanelDrag); } // 创建主控制面板 function createControlPanel() { if (document.getElementById('batch-select-panel-v8')) return; checkIsSharePage(); const panel = document.createElement('div'); panel.id = 'batch-select-panel-v8'; const { left, top } = getUIPosition(); const isHidden = getUIHidden(); isPanelVisible = !isHidden; let positionStyle = ''; if (left !== null && top !== null) { positionStyle = `left: ${left}px; top: ${top}px; right: auto; bottom: auto;`; } else { positionStyle = `right: 20px; top: 80px;`; } panel.style.cssText = ` position: fixed; ${positionStyle} width: 400px; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 15px rgba(0,0,0,0.2); z-index: 10000; font-family: "Microsoft YaHei", Arial, sans-serif; font-size: 13px; display: ${isHidden ? 'none' : 'block'}; `; const pageTypeText = isSharePage ? '📁 分享页面' : '💾 网盘主页'; const savedPos = getLastPosition(); const posText = savedPos > 0 ? `第 ${savedPos} 个` : '未设置'; panel.innerHTML = `
📂 批量选择助手
${pageTypeText} 空闲
📊 当前已选中: 0
📄 文件夹内文件数: 0
📍 上次选择位置: ${posText}
🎯 目标数量: 0 / ${targetCount}
📍 起始位置设置
💡 提示:位置指第几个文件(1=第一个),0表示从第1个开始
📝 运行日志
`; document.body.appendChild(panel); // 绑定拖动事件 const header = panel.querySelector('.panel-header'); if (header) { header.addEventListener('mousedown', startDrag); } // 绑定其他事件 const startBtn = document.getElementById('start-select-btn'); const stopBtn = document.getElementById('stop-select-btn'); const clearBtn = document.getElementById('clear-select-btn'); const resetPosBtn = document.getElementById('reset-position-btn'); const setPosBtn = document.getElementById('set-position-btn'); const loadPosBtn = document.getElementById('load-position-btn'); const refreshBtn = document.getElementById('refresh-count-btn'); const detectBtn = document.getElementById('detect-files-btn'); const clearLogBtn = document.getElementById('clear-log-btn'); const hideBtn = document.getElementById('hide-panel-btn'); if (startBtn) startBtn.onclick = () => startAutoSelect(); if (stopBtn) stopBtn.onclick = () => stopAutoSelect(); if (clearBtn) clearBtn.onclick = () => clearAllSelection(); if (resetPosBtn) resetPosBtn.onclick = () => resetSelectionPosition(); if (setPosBtn) setPosBtn.onclick = () => { const input = document.getElementById('position-input'); if (input && input.value) { const pos = parseInt(input.value, 10); if (!isNaN(pos)) { setManualPosition(pos); } else { addLog('请输入有效的数字', 'error'); } } else { addLog('请输入起始位置', 'error'); } }; if (loadPosBtn) loadPosBtn.onclick = () => { const savedPos = loadPositionToInput(); if (savedPos > 0) { setManualPosition(savedPos); } }; if (refreshBtn) refreshBtn.onclick = () => refreshSelectedDisplay(); if (detectBtn) detectBtn.onclick = () => { const fileCount = getFileRows().length; const selectedCount = getSelectedCount(); addLog(`重新检测: 共 ${fileCount} 个文件,已选中 ${selectedCount} 个`); refreshSelectedDisplay(); }; if (clearLogBtn) clearLogBtn.onclick = () => { logContent = []; const textarea = document.getElementById('log-textarea'); if (textarea) textarea.value = ''; addLog('日志已清空'); }; if (hideBtn) hideBtn.onclick = () => togglePanel(); logPanel = panel; setTimeout(() => { refreshSelectedDisplay(); const fileCount = getFileRows().length; addLog(`初始化完成,检测到 ${fileCount} 个文件/文件夹`); loadPositionToInput(); }, 500); } // 定期刷新显示 let displayRefreshTimer = null; function startDisplayRefresh() { if (displayRefreshTimer) clearInterval(displayRefreshTimer); displayRefreshTimer = setInterval(() => { if (document.getElementById('batch-select-panel-v8') && isPanelVisible) { refreshSelectedDisplay(); } }, 2000); } // 确保面板存在 function ensurePanel() { if (!document.getElementById('batch-select-panel-v8')) { createControlPanel(); createFloatBall(); startDisplayRefresh(); } } // 监听滚动 function setupScrollObserver() { const container = document.querySelector('[node-type="NHcGw"], .vdAfKMb, .file-list-container'); if (container && !container._scrollObserver) { container._scrollObserver = true; container.addEventListener('scroll', () => { if (container.scrollTop + container.clientHeight >= container.scrollHeight - 200) { setTimeout(() => { refreshSelectedDisplay(); }, 1000); } }); } } // 初始化 function init() { checkIsSharePage(); const checkInterval = setInterval(() => { const rows = getFileRows(); if (rows.length > 0) { clearInterval(checkInterval); ensurePanel(); setupScrollObserver(); addLog(`批量选择助手已启动(悬浮球版),检测到 ${rows.length} 个文件`); const savedPos = getLastPosition(); if (savedPos > 0) { addLog(`📌 上次选择到第 ${savedPos} 个文件,下次将从第 ${savedPos + 1} 个开始`, 'info'); } } }, 1000); setTimeout(() => { clearInterval(checkInterval); if (!document.getElementById('batch-select-panel-v8')) { ensurePanel(); addLog('页面加载较慢,请手动刷新文件列表后点击"重新检测文件"', 'warning'); } }, 10000); let lastUrl = location.href; const observer = new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; addLog('检测到页面切换,重新初始化...'); checkIsSharePage(); setTimeout(() => { ensurePanel(); refreshSelectedDisplay(); setupScrollObserver(); loadPositionToInput(); }, 1500); } }); observer.observe(document.body, { subtree: true, childList: true }); } init(); })();