// ==UserScript== // @name 课程自动翻页导航 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 自动滚动页面 + 点击"下一页"按钮,适用于自有网站课程内容管理 // @author You // @match https://avaryholding.yunxuetang.cn/* // @grant none // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; // ==================== 配置区 ==================== const CONFIG = { // 下一页按钮的选择器(按优先级排列,命中第一个就点击) nextButtonSelectors: [ // ★ 云学堂 "下一个"按钮 "button:has-text('下一个')", "button.yxtf-button.is-icon", ".yxtf-button.is-icon", ], // 每页之间等待时间(毫秒) waitBetweenPages: 3000, // 是否自动滚动页面到底部 scrollToBottom: true, scrollStep: 300, scrollDelay: 300, // 最多翻多少页(防无限循环) maxPages: 50, // 是否自动开始 autoStart: true, // 是否显示状态面板 showPanel: true, // ---- SPA 相关 ---- // 点击后等待按钮消失的最长时间(毫秒) waitForOldBtnGone: 5000, // 点击后等待新按钮出现的最大等待时间(毫秒) waitForNewBtnAppear: 15000, // 轮询间隔(毫秒) pollInterval: 500, // ---- 课程完成检测 ---- // 倒计时选择器(云学堂剩余时间 span) countdownSelector: 'span.yxt-color-warning', // 课程标题选择器 courseTitleSelector: '.yxtulcdsdk-course-player_title, h3.course-name, .course-title', // 剩余时间小于此值(秒)自动跳下一个,设 0 表示不等倒计时 autoAdvanceBelowSeconds: 999999, // 倒计时归零后等待时间再点下一个(毫秒) doneWaitMs: 2000, // 面板刷新间隔(毫秒) panelRefreshMs: 1000, }; // ================== 配置区结束 ================== let pageCount = 0; let running = false; let stopFlag = false; let timerInterval = null; let waitingForCompletion = false; let debugCountdownId = null; let fakeRemainSeconds = null; let forceNextFlag = false; // debug 计时器归零时,强制触发翻页 let completedCourses = []; // 已完成课程列表 // ---------- 状态面板 ---------- function createPanel() { if (!CONFIG.showPanel) return; const panel = document.createElement('div'); panel.id = 'auto-nav-panel'; panel.innerHTML = `
🎓 课程导航面板
📘 课程:
---
⏱ 剩余时间: ---
📋 课程状态: 检测中...
📊 总进度: ---
📄 页面: 0 / ${CONFIG.maxPages}
🔘 按钮: 检测中...
等待就绪...
⚠ 仅供个人学习测试使用
禁止用于违反平台规则的行为
使用者自行承担全部责任
`; document.body.appendChild(panel); document.getElementById('an-btn-start').addEventListener('click', start); document.getElementById('an-btn-stop').addEventListener('click', stop); document.getElementById('an-btn-debug').addEventListener('click', toggleDebug); // ---- 折叠/展开 ---- panel.querySelector('.title').addEventListener('click', function(e) { panel.classList.toggle('collapsed'); }); // 默认收起 panel.classList.add('collapsed'); // ---- 面板拖拽 ---- makeDraggable(panel); } function makeDraggable(el) { const titleBar = el.querySelector('.title'); if (!titleBar) return; let startX, startY, startLeft, startTop, dragging = false; titleBar.addEventListener('mousedown', function(e) { if (e.target.tagName === 'BUTTON') return; // 不拦截按钮点击 dragging = true; e.preventDefault(); const rect = el.getBoundingClientRect(); startX = e.clientX; startY = e.clientY; startLeft = rect.left; startTop = rect.top; el.style.transition = 'none'; // 切换到 left 定位,清除 right el.style.right = 'auto'; el.style.left = startLeft + 'px'; }); document.addEventListener('mousemove', function(e) { if (!dragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; el.style.left = Math.max(0, Math.min(startLeft + dx, window.innerWidth - el.offsetWidth)) + 'px'; el.style.top = Math.max(0, Math.min(startTop + dy, window.innerHeight - 40)) + 'px'; }); document.addEventListener('mouseup', function() { if (dragging) { dragging = false; el.style.transition = ''; } }); } function updatePanel(nextText, status) { // page count const pi = document.getElementById('an-page-info'); if (pi) pi.textContent = pageCount + ' / ' + CONFIG.maxPages; // button info const bi = document.getElementById('an-btn-info'); if (bi) bi.textContent = nextText || '---'; // status const st = document.getElementById('an-status'); if (st) { st.textContent = status || ''; st.className = 'status'; if (running) st.className += ' running'; } } function updateCourseInfo() { const info = getCourseInfo(); // 课程名 const cn = document.getElementById('an-course-name'); if (cn) { const prefix = info.isCompleted ? '✅ ' : ''; cn.textContent = prefix + (info.courseName || '(未检测到)'); } // 当前课程状态 const cs = document.getElementById('an-course-status'); if (cs) { if (info.isCompleted) { cs.textContent = '✅ 已完成'; cs.className = 'value done'; } else if (info.remainSeconds !== null && info.remainSeconds > 0) { cs.textContent = '⏳ 学习中'; cs.className = 'value'; cs.style.color = '#ff9800'; } else { cs.textContent = '📄 浏览中'; cs.className = 'value'; cs.style.color = '#4fc3f7'; } } // 总进度(从页面提取) const pg = document.getElementById('an-progress'); if (pg) { pg.textContent = getProgressText(); } // 剩余时间(始终显示页面真实时间) const rt = document.getElementById('an-remain-time'); if (rt) { if (info.remainSeconds === null) { rt.textContent = '---'; rt.className = 'value countdown'; } else if (info.remainSeconds <= 0) { rt.textContent = '✅ 已完成'; rt.className = 'value done'; } else { rt.textContent = formatTime(info.remainSeconds); rt.className = 'value countdown'; if (info.remainSeconds < 60) rt.className += ' warn'; } } // debug 加速倒计时显示(在独立按钮区域) const di = document.getElementById('an-debug-info'); if (di) { if (fakeRemainSeconds !== null) { if (fakeRemainSeconds <= 0) { di.textContent = '✅ 即将跳转'; di.className = 'value done'; } else { di.textContent = formatTime(fakeRemainSeconds); di.className = 'value warn'; } } else { di.textContent = '---'; di.className = 'value warn'; } } } function setRunningUI(run) { const startBtn = document.getElementById('an-btn-start'); const stopBtn = document.getElementById('an-btn-stop'); const debugSection = document.getElementById('an-debug-section'); if (startBtn) startBtn.style.display = run ? 'none' : 'block'; if (stopBtn) stopBtn.style.display = run ? 'block' : 'none'; // debug 区域只在运行后显示,停止时隐藏 if (debugSection) debugSection.style.display = run ? 'block' : 'none'; // 停止时也清掉 debug 状态 if (!run) { stopDebugCountdown(); const debugBtn = document.getElementById('an-btn-debug'); if (debugBtn) { debugBtn.textContent = '⚡ 启动加速跳转'; debugBtn.className = 'btn-debug'; } } } // ---------- 查找下一页按钮 ---------- function findNextButton() { for (const selector of CONFIG.nextButtonSelectors) { try { // has-text 是 Playwright 语法,浏览器不支持,需要转换 const el = findElement(selector); if (el && isVisible(el)) { return { el, selector }; } } catch (e) { // selector parse error, skip } } return null; } function findElement(selector) { // 处理 :has-text('xxx') 伪选择器 → 浏览器原生不支持,手动模拟 const hasTextMatch = selector.match(/:has-text\(['"](.+?)['"]\)/); if (hasTextMatch) { const baseSelector = selector.replace(/:has-text\(['"].+?['"]\)/, '').trim(); const text = hasTextMatch[1]; const elements = document.querySelectorAll(baseSelector || '*'); for (const el of elements) { if (el.textContent && el.textContent.includes(text)) { return el; } } return null; } return document.querySelector(selector); } function isVisible(el) { if (!el) return false; const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && el.offsetWidth > 0 && el.offsetHeight > 0; } // ---------- 滚动页面 ---------- async function scrollPage() { if (!CONFIG.scrollToBottom) return; return new Promise((resolve) => { let prevHeight = 0; let attempts = 0; const maxAttempts = 50; function doScroll() { const currentHeight = document.body.scrollHeight; if (currentHeight === prevHeight || attempts >= maxAttempts) { window.scrollTo(0, 0); resolve(); return; } prevHeight = currentHeight; window.scrollBy(0, CONFIG.scrollStep); attempts++; setTimeout(doScroll, CONFIG.scrollDelay); } doScroll(); }); } // ---------- 课程信息提取 ---------- /** 从页面提取课程名和剩余时间 */ function getCourseInfo() { const info = { courseName: null, remainSeconds: null, isCompleted: false }; // 1. 提取课程名称(云学堂 .yxtf-tooltip 带 ulcdsdk-ellipsis 的 span) const titleSelectors = [ 'span.yxtf-tooltip.ulcdsdk-ellipsis-2', 'span.yxtf-tooltip[class*="ulcdsdk-ellipsis"]', '.group-title', '.yxtulcdsdk-course-player_title', 'h3.course-name', ]; for (const sel of titleSelectors) { const el = document.querySelector(sel); if (el && el.textContent.trim()) { info.courseName = el.textContent.trim().slice(0, 60); break; } } // 2. 检测课程是否已完成(通过 SVG path 图标或父级状态) info.isCompleted = detectCourseCompleted(); // 2. 提取剩余时间 const countEl = document.querySelector(CONFIG.countdownSelector); if (countEl) { const rawText = countEl.textContent.trim(); console.log('[面板] 倒计时原始文本:', JSON.stringify(rawText)); info.remainSeconds = parseCountdownText(rawText); } else { // 尝试更宽泛的匹配 const fallbackEl = document.querySelector('.yxt-color-warning'); if (fallbackEl) { const rawText = fallbackEl.textContent.trim(); console.log('[面板] 倒计时(fallback)原始文本:', JSON.stringify(rawText)); info.remainSeconds = parseCountdownText(rawText); } else { console.log('[面板] 未找到倒计时元素'); } } return info; } /** 检测当前课程是否已完成(通过页面图标/状态判断) */ function detectCourseCompleted() { // 方式1: 侧边栏当前高亮项带完成图标 // 云学堂已完成项的 SVG path 通常在一个容器内,查找完成标记 const doneIndicators = [ '.yxt-color-success', // 绿色文字/图标 = 已完成 '[class*="success"] svg path', // success class 内的 SVG '[class*="done"] svg path', // done class 内的 SVG '[class*="completed"] svg path', // completed class 内的 SVG '.yxtulcdsdk-item.is-active.is-completed', // 当前激活且已完成 '.yxtulcdsdk-item.active.completed', '.task-item.completed', ]; for (const sel of doneIndicators) { const el = document.querySelector(sel); if (el && isVisible(el)) return true; } // 方式2: 倒计时区域没有剩余时间(已经学完)→ 但有完成标记 const countEl = document.querySelector(CONFIG.countdownSelector); if (!countEl) { // 无倒计时 → 检查是否有"已完成"/"已学完"文字 const doneTextEls = document.querySelectorAll( 'span, div, .yxtbiz-language-slot, .yxtulcdsdk-course-player_countdown' ); for (const el of doneTextEls) { const t = el.textContent || ''; if (t.includes('已完成') || t.includes('已学完') || t.includes('已通过')) { return true; } } } // 方式3: 查找侧边栏中当前高亮的课程项,看是否有完成图标 const activeItem = document.querySelector( '.yxtulcdsdk-item.is-active, .yxtulcdsdk-item.active, [class*="is-active"][class*="task"]' ); if (activeItem) { // 在当前激活项内查找完成标记 SVG const doneSvg = activeItem.querySelector('svg[class*="success"], [class*="done"], [class*="check"]'); if (doneSvg) return true; // 或者查找绿色 path 元素 const greenPath = activeItem.querySelector('path[fill*="#4caf50"], path[fill*="#52c41a"], path[stroke*="#4caf50"]'); if (greenPath) return true; } return false; } /** 从页面提取总进度 如 "7%(7/98个任务)" */ function getProgressText() { const selector = 'span.text-8c.ml12.font-size-12, span.text-8c.ml12, span.text-8c'; const el = document.querySelector(selector); if (el) { const t = el.textContent.trim(); const pctMatch = t.match(/(\d+)%/); if (pctMatch) { const pct = pctMatch[1]; const fracMatch = t.match(/(\d+)\s*\/\s*(\d+)/); if (fracMatch) { return pct + '% (' + fracMatch[1] + '/' + fracMatch[2] + ')'; } return pct + '%'; } return t.slice(0, 30); } return '---'; } /** 解析倒计时文字为秒数 */ function parseCountdownText(text) { if (!text) return null; // 已经是完成状态 → 返回 0 if (/已完成|已学完|已通过|恭喜|完成/.test(text)) return 0; // "X小时Y分Z秒" 如 "1小时30分05秒" → 5405 let total = 0; const hourMatch = text.match(/(\d+)\s*小时/); const minMatch = text.match(/(\d+)\s*分/); const secMatch = text.match(/(\d+)\s*秒/); if (hourMatch) total += parseInt(hourMatch[1]) * 3600; if (minMatch) total += parseInt(minMatch[1]) * 60; if (secMatch) total += parseInt(secMatch[1]); if (total > 0) return total; // "XX:YY:ZZ" 如 "1:30:05" → 5405 const fullTime = text.match(/(\d+):(\d{2}):(\d{2})/); if (fullTime) return parseInt(fullTime[1]) * 3600 + parseInt(fullTime[2]) * 60 + parseInt(fullTime[3]); // "XX:YY" 如 "12:30" → 750 const timeMatch = text.match(/(\d+):(\d{2})/); if (timeMatch) return parseInt(timeMatch[1]) * 60 + parseInt(timeMatch[2]); // "XX YY" 空格分隔两个数字,如 "75 33" → 75分33秒 → 4533 const twoNumMatch = text.match(/^(\d+)\s+(\d+)/); if (twoNumMatch) { const a = parseInt(twoNumMatch[1]); const b = parseInt(twoNumMatch[2]); if (b < 60) return a * 60 + b; return Math.max(a, b); } // 单个纯数字 → 秒数 const numMatch = text.match(/^[\s]*(\d+)[\s]*$/); if (numMatch) return parseInt(numMatch[1]); // 第一个数字(兜底) const firstNum = text.match(/(\d+)/); if (firstNum) return parseInt(firstNum[1]); return null; } /** 格式化秒数为可读文本,超过1小时才显示小时 */ function formatTime(seconds) { if (seconds < 60) return seconds + ' 秒'; if (seconds < 3600) { const m = Math.floor(seconds / 60); const s = seconds % 60; return s === 0 ? m + ' 分钟' : m + '分' + s + '秒'; } const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; if (m === 0 && s === 0) return h + ' 小时'; if (s === 0) return h + '小时' + m + '分'; return h + '小时' + m + '分' + s + '秒'; } // ---------- 实时计时器 ---------- let _timerRunning = false; function startTimer() { stopTimer(); updateCourseInfo(); _timerRunning = true; tickTimer(); } function tickTimer() { if (!_timerRunning) return; updateCourseInfo(); timerInterval = setTimeout(tickTimer, CONFIG.panelRefreshMs); } function stopTimer() { _timerRunning = false; if (timerInterval) { clearTimeout(timerInterval); timerInterval = null; } } // ---------- 课程完成检测 ---------- let _hadCountdown = false; // 记住当前页面曾经有倒计时 function isCourseDone() { const info = getCourseInfo(); // 倒计时归零 if (info.remainSeconds !== null && info.remainSeconds <= 0) return true; // 倒计时元素消失 → 说明页面把它移走了(课程完成) if (_hadCountdown && info.remainSeconds === null) return true; // 页面有已完成标记 if (info.isCompleted) return true; return false; } /** 正常等待课程倒计时结束 */ async function waitForCourseCompletion() { waitingForCompletion = true; updatePanel('⏳ 等待中', '课程学习中...'); let lastRemain = -1; // 上次余数 let staleCount = 0; // 连续不变次数 const staleMax = 15; // 连续15秒不动→卡死,强跳 while (!stopFlag) { updateCourseInfo(); if (isCourseDone() || forceNextFlag) { updatePanel('✅ 已完成', '准备跳转...'); await sleep(CONFIG.doneWaitMs); waitingForCompletion = false; return true; } // 卡死检测:倒计时连续N次不变 const info = getCourseInfo(); if (info.remainSeconds !== null) { if (info.remainSeconds === lastRemain) { staleCount++; if (staleCount >= staleMax) { console.log('[卡死检测] 倒计时 %d 秒未变化,强制跳转', lastRemain); updatePanel('⚠ 页面卡死', '强制跳转...'); await sleep(CONFIG.doneWaitMs); waitingForCompletion = false; return true; } } else { staleCount = 0; } lastRemain = info.remainSeconds; } await sleep(CONFIG.panelRefreshMs); } waitingForCompletion = false; return false; } // ---------- Debug 独立加速计时器 ---------- function startDebugCountdown() { stopDebugCountdown(); const info = getCourseInfo(); const original = info.remainSeconds; if (original === null || original <= 0) { updatePanel('⚠ 无需加速', '倒计时已完成'); return; } const secPart = Math.max(1, original % 60); forceNextFlag = false; fakeRemainSeconds = secPart; updateCourseInfo(); console.log(`[debug] 原${formatTime(original)} → 取秒位 ${secPart}秒`); const btn = document.getElementById('an-btn-debug'); if (btn) { btn.textContent = '⏹ 取消加速'; btn.className = 'btn-debug running-debug'; } debugCountdownId = setInterval(() => { if (stopFlag || !running) { stopDebugCountdown(); return; } fakeRemainSeconds--; updateCourseInfo(); if (fakeRemainSeconds <= 0) { // 先设 flag,再清 timer(不清 flag!) forceNextFlag = true; fakeRemainSeconds = 0; updateCourseInfo(); console.log('[debug] ⏰ 加速计时器归零,触发强制翻页'); // 只清 timer 和 UI,保留 forceNextFlag 供主循环检测 if (debugCountdownId) { clearInterval(debugCountdownId); debugCountdownId = null; } fakeRemainSeconds = null; const di = document.getElementById('an-debug-info'); if (di) { di.textContent = '✅ 触发跳转'; di.className = 'value done'; } const b = document.getElementById('an-btn-debug'); if (b) { b.textContent = '⚡ 启动加速跳转'; b.className = 'btn-debug'; } } }, 1000); } function stopDebugCountdown() { if (debugCountdownId) { clearInterval(debugCountdownId); debugCountdownId = null; } fakeRemainSeconds = null; forceNextFlag = false; // 手动取消时才重置 const di = document.getElementById('an-debug-info'); if (di) { di.textContent = '---'; di.className = 'value warn'; } const btn = document.getElementById('an-btn-debug'); if (btn) { btn.textContent = '⚡ 启动加速跳转'; btn.className = 'btn-debug'; } } /** 切换 debug 加速开/关 */ function toggleDebug() { if (debugCountdownId) { // 正在运行 → 取消 stopDebugCountdown(); updatePanel('取消加速', '正常等待中...'); } else { // 启动 startDebugCountdown(); } } /** 轮询等待按钮出现,返回按钮元素,超时返回 null */ async function waitForButton(timeoutMs) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (stopFlag) return null; const result = findNextButton(); if (result) return result; await sleep(CONFIG.pollInterval); } return null; } /** 轮询等待按钮从 DOM 中消失,超时也继续 */ async function waitForButtonGone(oldBtn, timeoutMs) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (stopFlag) return; if (!document.contains(oldBtn)) return; // 已从 DOM 移除 if (!isVisible(oldBtn)) return; // 已隐藏 await sleep(300); } } // ---------- 主流程 ---------- async function navigateOnePage() { pageCount++; stopDebugCountdown(); // 进新页面先清旧加速 timer(手动取消那种) forceNextFlag = false; // 新页面开始,flag 重置 _hadCountdown = false; // 新页面,重置倒计时标记 updatePanel(`按钮: 等待出现...`, `第 ${pageCount} 节`); // 1. 等待当前页面的"下一个"按钮出现 const result = await waitForButton(CONFIG.waitForNewBtnAppear); if (!result) { updatePanel('⚠ 超时', `第${pageCount}节无按钮`); console.log('[自动翻页] 等待超时,未找到按钮'); return false; } const { el, selector } = result; updatePanel(`按钮: ${selector}`, `第 ${pageCount} 节`); // 2. 检测课程是否已完成 → 已完成可直接跳过 const courseInfo = getCourseInfo(); if (courseInfo.isCompleted && courseInfo.courseName && !completedCourses.includes(courseInfo.courseName)) { completedCourses.push(courseInfo.courseName); console.log(`[跳过] ${courseInfo.courseName} 已完成`); // 10 秒倒计时提示 for (let i = 10; i >= 1; i--) { if (stopFlag) return false; updatePanel('⏭ 已完成课程', `${i}秒后自动跳过...`); await sleep(1000); } const skipBtn = findNextButton(); if (skipBtn) { updatePanel('⏭ 跳过中', '该课程已完成'); skipBtn.el.click(); await sleep(CONFIG.waitBetweenPages); } return true; } // 3. 检查是否有倒计时 → 等待完成(或被加速中断) if (courseInfo.remainSeconds !== null && courseInfo.remainSeconds > 0) { _hadCountdown = true; // 标记:当前页面有倒计时 updatePanel(`⏳ 剩余 ${formatTime(courseInfo.remainSeconds)}`, `等待完成...`); const completed = await waitForCourseCompletion(); if (!completed || stopFlag) return false; // 课程完成 → 记录 if (courseInfo.courseName && !completedCourses.includes(courseInfo.courseName)) { completedCourses.push(courseInfo.courseName); console.log(`[完成] ${courseInfo.courseName} 已累计 ${completedCourses.length} 门`); } // 加速触发 → 直接跳转,跳过滚动 if (forceNextFlag) { console.log('[加速] 倒计时归零,直接跳转'); updatePanel('⚡ 加速跳转', '直接跳转...'); const jumpBtn = findNextButton(); if (jumpBtn) { jumpBtn.el.click(); await sleep(CONFIG.waitBetweenPages); } return true; } } // 3. 正常流程:滚动页面 await scrollPage(); // 记录课程(无倒计时的页面,滚完就算完成) if (!courseInfo.remainSeconds && courseInfo.courseName && !completedCourses.includes(courseInfo.courseName)) { completedCourses.push(courseInfo.courseName); } // 4. 点击按钮 const finalResult = findNextButton(); if (!finalResult) { updatePanel('⚠ 按钮消失', `第${pageCount}节`); return false; } updatePanel(`点击: ${finalResult.selector}`, `跳转中...`); finalResult.el.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(500); finalResult.el.click(); console.log(`[自动翻页] 第${pageCount}节 → 点击: ${finalResult.selector}`); // 5. 等待内容切换 await waitForButtonGone(el, CONFIG.waitForOldBtnGone); await sleep(CONFIG.waitBetweenPages); return true; } async function run() { if (pageCount >= CONFIG.maxPages) { console.log(`[自动翻页] 已达最大页数 ${CONFIG.maxPages}`); stop(); return; } if (stopFlag) { stop(); return; } try { const hasNext = await navigateOnePage(); if (!hasNext) { stop(); return; } } catch (e) { console.error('[自动翻页] 出错:', e); updatePanel('❌ 出错', String(e).slice(0, 30)); stop(); return; } if (!stopFlag) { setTimeout(run, 300); } } // ---------- 控制 ---------- function start() { if (running) return; running = true; stopFlag = false; pageCount = 0; completedCourses = []; setRunningUI(true); startTimer(); // 自动展开面板 document.getElementById('auto-nav-panel').classList.remove('collapsed'); updatePanel('启动中...', '开始自动翻页'); console.log('[自动翻页] 开始'); run(); } function stop() { running = false; stopFlag = true; waitingForCompletion = false; stopDebugCountdown(); stopTimer(); setRunningUI(false); updatePanel('已停止', `共 ${pageCount} 节`); updateCourseInfo(); console.log(`[自动翻页] 停止,共处理 ${pageCount} 节`); } function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } // ---------- 入口 ---------- function init() { createPanel(); startTimer(); // 立即开始刷新课程信息 // 异步检测按钮 updatePanel('检测中...', '等待页面就绪'); waitForButton(10000).then(result => { if (result) { updatePanel(`✅ 按钮: ${result.selector}`, '可以开始了'); } else { updatePanel('⚠ 未匹配', '请检查选择器'); } }); if (CONFIG.autoStart) { setTimeout(start, 2000); } } // 页面加载完成后初始化 if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } })();