// ==UserScript== // @name 华医网课程自动切换工具(修复自动跳转) // @namespace https://github.com/user/auto-next-lesson-tool // @version 2.3.0 // @description 修复课程完成后自动跳转下一节课功能,增强按钮检测和页面切换机制 // @author Your Name // @match *://*.91huayi.com/* // @match *://yxpk.91huayi.com/* // @match *://xuexi.91huayi.com/* // @match *://*.huayiwang91.com/* // @icon data:image/svg+xml,🎓 // @grant none // @license MIT // @run-at document-idle // ==/UserScript== class HuaYiAutoNextLesson { constructor(options = {}) { this.options = { switchDelay: 2, checkInterval: 0.8, progressThreshold: 0.95, debugMode: false, autoMute: true, showControlPanel: true, panelPosition: 'top-right', autoStart: true, // 新增:自动启动 retryTimes: 5, // 新增:重试次数 onStateChange: null, onVideoDetected: null, onVideoEnded: null, onLessonSwitched: null, onError: null }; Object.assign(this.options, options); // 状态变量 this.isRunning = false; this.checkTimer = null; this.switchTimer = null; this.lastVideoState = null; this.switchHistory = []; this.currentCourseTitle = ''; this.currentLessonTitle = ''; this.controlPanel = null; this.dragState = { isDragging: false, offsetX: 0, offsetY: 0 }; this.retryCount = 0; // 新增:重试计数 this.isSwitching = false; // 新增:防止重复切换 // 优化选择器 - 增强下一课按钮选择器 this.selectors = { videoElements: [ 'video', '.vjs-tech', '.video-container > video', '#video-player > video', 'iframe[src*="video"]', // 新增:iframe视频 '.prism-player' // 新增:阿里云播放器 ], nextLessonButtons: [ // 华医网常见按钮文本和选择器 '.next-btn', '.next-lesson', '.btn-next', '.next', '.next-course', '.continue-btn', 'button:contains("下一课")', 'button:contains("下一步")', 'button:contains("下一节")', 'button:contains("继续学习")', 'button:contains("继续")', 'a:contains("下一课")', 'a:contains("下一步")', '.layui-btn:contains("下一课")', '.btn-primary:contains("下一课")', '[onclick*="next"]', '[onclick*="Next"]', '.course-next', '.lesson-next' ], popupCloseButtons: [ '.close-btn', '.close', '[data-dismiss="modal"]', '.layui-layer-close', '.layui-layer-btn', '.modal-close', 'button:contains("确定")', 'button:contains("关闭")', 'button:contains("知道了")' ], progressBars: [ '.progress-bar', '.vjs-play-progress', '.course-progress', '.study-progress' ], courseTitle: [ '.course-title', '.cur-course-name', '.course-name', 'h1', 'h2' ], lessonTitle: [ '.lesson-title', '.cur-lesson-name', '.video-title', 'h3', 'h4' ], // 新增:课程完成标记 completeMarkers: [ '.course-complete', '.study-complete', '.lesson-complete', '[class*="complete"]', '[class*="finished"]' ] }; this.log('华医网课程自动切换工具已初始化', 'info'); this._detectCurrentPage(); if (this.options.showControlPanel) { this._forceCreateControlPanel(); } // 自动启动 if (this.options.autoStart) { setTimeout(() => this.start(), 1000); } } _forceCreateControlPanel() { if (document.getElementById('huayi-auto-next-control-panel')) { this.controlPanel = document.getElementById('huayi-auto-next-control-panel'); this.log('控制面板已存在,复用现有面板', 'info'); this._bindControlPanelEvents(); this._updateControlPanelState(); return; } this.controlPanel = document.createElement('div'); this.controlPanel.id = 'huayi-auto-next-control-panel'; this.controlPanel.className = 'huayi-control-panel'; const panelStyles = ` position: fixed !important; background: rgba(255, 255, 255, 0.95) !important; border: 1px solid #e5e7eb !important; border-radius: 8px !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; padding: 10px !important; width: 280px !important; z-index: 999999 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; font-size: 12px !important; transition: all 0.3s ease !important; backdrop-filter: blur(8px) !important; display: block !important; `; let positionStyles = ''; switch (this.options.panelPosition) { case 'top-left': positionStyles = 'top: 20px; left: 20px; right: auto; bottom: auto;'; break; case 'bottom-left': positionStyles = 'bottom: 20px; left: 20px; right: auto; top: auto;'; break; case 'bottom-right': positionStyles = 'bottom: 20px; right: 20px; left: auto; top: auto;'; break; case 'top-right': default: positionStyles = 'top: 20px; right: 20px; left: auto; bottom: auto;'; break; } this.controlPanel.style.cssText = panelStyles + positionStyles; this.controlPanel.innerHTML = `
华医网自动切换工具
状态: 未启动
视频检测: 未检测
当前课程: ${this.currentCourseTitle || '未知'}
%
最近日志:
工具已初始化
`; try { document.body.appendChild(this.controlPanel); this.log('控制面板已插入到页面', 'success'); } catch (err) { document.documentElement.appendChild(this.controlPanel); this.log('body插入失败,已插入到html根节点', 'warning'); } this._bindControlPanelEvents(); this._updateControlPanelState(); } _detectCurrentPage() { const url = window.location.href.toLowerCase(); if (url.includes('/course/') || url.includes('/lesson/') || url.includes('/study/')) { this.log('检测到课程播放页面', 'info'); this._extractPageInfo(); } else { this.log('检测到其他页面', 'info'); } } _extractPageInfo() { for (const selector of this.selectors.courseTitle) { try { const element = document.querySelector(selector); if (element && element.textContent.trim()) { this.currentCourseTitle = element.textContent.trim(); break; } } catch (error) { this.log(`提取课程标题出错: ${error.message}`, 'debug'); } } for (const selector of this.selectors.lessonTitle) { try { const element = document.querySelector(selector); if (element && element.textContent.trim()) { this.currentLessonTitle = element.textContent.trim(); break; } } catch (error) { this.log(`提取课时标题出错: ${error.message}`, 'debug'); } } this.log(`当前课程: ${this.currentCourseTitle || '未知'}`, 'info'); this.log(`当前课时: ${this.currentLessonTitle || '未知'}`, 'info'); this._updateControlPanelInfo(); } _bindControlPanelEvents() { if (!this.controlPanel) return; this.controlPanel.addEventListener('click', (e) => { const target = e.target.closest('button'); if (!target) return; switch (target.id) { case 'huayi-btn-start': this.start(); this._updateControlPanelState(); break; case 'huayi-btn-stop': this.stop(); this._updateControlPanelState(); break; case 'huayi-btn-mute': this.options.autoMute = !this.options.autoMute; target.textContent = this.options.autoMute ? '静音: 开' : '静音: 关'; this.updateOptions({ autoMute: this.options.autoMute }); this.log(`${this.options.autoMute ? '开启' : '关闭'}自动静音`, 'info'); break; case 'huayi-btn-debug': this.options.debugMode = !this.options.debugMode; target.textContent = this.options.debugMode ? '调试: 开' : '调试: 关'; this.updateOptions({ debugMode: this.options.debugMode }); this.log(`${this.options.debugMode ? '开启' : '关闭'}调试模式`, 'info'); break; case 'huayi-btn-clear-logs': this._clearLogs(); break; case 'huayi-btn-minimize': this._togglePanelMinimize(); break; default: break; } }); this._makePanelDraggable(); } _makePanelDraggable() { if (!this.controlPanel) return; const header = this.controlPanel.querySelector('.panel-header'); if (!header) return; header.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { return; } this.dragState.isDragging = true; const rect = this.controlPanel.getBoundingClientRect(); this.dragState.offsetX = e.clientX - rect.left; this.dragState.offsetY = e.clientY - rect.top; this.controlPanel.style.cursor = 'grabbing'; header.style.userSelect = 'none'; }); const handleMouseMove = (e) => { if (!this.dragState.isDragging) return; requestAnimationFrame(() => { const x = e.clientX - this.dragState.offsetX; const y = e.clientY - this.dragState.offsetY; const maxX = window.innerWidth - this.controlPanel.offsetWidth; const maxY = window.innerHeight - this.controlPanel.offsetHeight; const boundedX = Math.max(0, Math.min(x, maxX)); const boundedY = Math.max(0, Math.min(y, maxY)); this.controlPanel.style.left = boundedX + 'px'; this.controlPanel.style.top = boundedY + 'px'; this.controlPanel.style.right = 'auto'; this.controlPanel.style.bottom = 'auto'; }); }; const handleMouseUp = () => { if (this.dragState.isDragging) { this.dragState.isDragging = false; this.controlPanel.style.cursor = 'default'; header.style.userSelect = ''; } }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); header.addEventListener('dragstart', (e) => e.preventDefault()); } _togglePanelMinimize() { const content = document.getElementById('huayi-panel-content'); const minimizeBtn = document.getElementById('huayi-btn-minimize'); if (!content || !minimizeBtn) return; if (content.style.display === 'none') { content.style.display = 'block !important'; minimizeBtn.textContent = '▼'; } else { content.style.display = 'none !important'; minimizeBtn.textContent = '▲'; } } _updateControlPanelState() { if (!this.controlPanel) return; const statusIndicator = document.getElementById('huayi-status-indicator'); const statusValue = document.getElementById('huayi-status-value'); const startBtn = document.getElementById('huayi-btn-start'); const stopBtn = document.getElementById('huayi-btn-stop'); if (statusIndicator && statusValue) { if (this.isRunning) { statusIndicator.style.backgroundColor = '#10b981'; statusValue.textContent = '运行中'; statusValue.style.color = '#10b981'; } else { statusIndicator.style.backgroundColor = '#9ca3af'; statusValue.textContent = '已停止'; statusValue.style.color = '#9ca3af'; } } if (startBtn && stopBtn) { if (this.isRunning) { startBtn.style.backgroundColor = '#9ca3af'; startBtn.disabled = true; stopBtn.style.backgroundColor = '#ef4444'; stopBtn.disabled = false; } else { startBtn.style.backgroundColor = '#10b981'; startBtn.disabled = false; stopBtn.style.backgroundColor = '#9ca3af'; stopBtn.disabled = true; } } } _updateControlPanelInfo() { if (!this.controlPanel) return; const courseTitleElement = document.getElementById('huayi-course-title'); if (courseTitleElement) { courseTitleElement.textContent = this.currentCourseTitle || '未知'; } } _updateVideoStatusDisplay(videoState) { if (!this.controlPanel) return; const videoStatusElement = document.getElementById('huayi-video-status'); if (!videoStatusElement) return; const progressPercent = videoState.exists ? Math.round(videoState.progress * 100) : 0; const displayText = videoState.exists ? `播放中 ${progressPercent}%` : '未检测'; const displayColor = videoState.exists ? (videoState.isPaused ? '#f59e0b' : '#10b981') : '#9ca3af'; if (videoStatusElement.textContent !== displayText || videoStatusElement.style.color !== displayColor) { videoStatusElement.textContent = displayText; videoStatusElement.style.color = displayColor; } } _addLogToPanel(message, level = 'info') { if (!this.controlPanel) return; const logsContainer = document.getElementById('huayi-logs-container'); if (!logsContainer) return; const logEntry = document.createElement('div'); logEntry.className = `log-entry ${level}`; logEntry.style.marginBottom = '2px'; logEntry.style.padding = '1px 0'; switch (level) { case 'error': logEntry.style.color = '#dc2626'; break; case 'warning': logEntry.style.color = '#d97706'; break; case 'success': logEntry.style.color = '#059669'; break; case 'debug': logEntry.style.color = '#6366f1'; break; default: logEntry.style.color = '#374151'; break; } const shortMessage = message.length > 60 ? message.substring(0, 60) + '...' : message; logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${shortMessage}`; logsContainer.insertBefore(logEntry, logsContainer.firstChild); const logEntries = logsContainer.querySelectorAll('.log-entry'); if (logEntries.length > 10) { logsContainer.removeChild(logEntries[logEntries.length - 1]); } logsContainer.scrollTop = 0; } _clearLogs() { const logsContainer = document.getElementById('huayi-logs-container'); if (logsContainer) { logsContainer.innerHTML = '
日志已清空
'; } } start() { if (this.isRunning) { this.log('工具已经在运行', 'warning'); return false; } this.isRunning = true; this.isSwitching = false; this.retryCount = 0; this.log('工具已启动', 'success'); this._notifyStateChange(); this._startVideoCheck(); this._showNotification('华医网课程自动切换工具已启动', '将自动检测视频并切换到下一课', 3000); this._updateControlPanelState(); return true; } stop() { if (!this.isRunning) { this.log('工具未运行', 'warning'); return false; } this.isRunning = false; this.isSwitching = false; this._clearResources(); this.log('工具已停止', 'info'); this._notifyStateChange(); this._updateControlPanelState(); return true; } updateOptions(newOptions) { Object.assign(this.options, newOptions); this.log('配置已更新: ' + JSON.stringify(newOptions), 'info'); if (this.isRunning) { this._clearResources(); this._startVideoCheck(); } return true; } getState() { return { isRunning: this.isRunning, options: { ...this.options }, switchHistory: [...this.switchHistory], currentCourseTitle: this.currentCourseTitle, currentLessonTitle: this.currentLessonTitle }; } getSwitchHistory() { return [...this.switchHistory]; } _findVideoElement() { // 查找视频元素 for (const selector of this.selectors.videoElements) { try { const elements = document.querySelectorAll(selector); for (const element of elements) { if (this._isElementVisible(element)) { // 如果是iframe,尝试获取内部的video if (element.tagName === 'IFRAME') { try { const iframeDoc = element.contentDocument || element.contentWindow.document; const iframeVideo = iframeDoc.querySelector('video'); if (iframeVideo) return iframeVideo; } catch (e) { // 跨域iframe无法访问 } } return element; } } } catch (error) { this.log(`选择器 "${selector}" 执行出错: ${error.message}`, 'debug'); } } return null; } _isElementVisible(element) { if (!document.body.contains(element)) return false; const rect = element.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) return false; const computedStyle = window.getComputedStyle(element); if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || computedStyle.opacity === '0') { return false; } return true; } _checkVideoState(videoElement) { try { const currentTime = videoElement.currentTime || 0; const duration = videoElement.duration || 0; const progress = duration > 0 ? currentTime / duration : 0; const isPaused = videoElement.paused; const isEnded = videoElement.ended || progress >= this.options.progressThreshold; const isUnexpectedPause = isPaused && !isEnded && currentTime > 0 && progress < this.options.progressThreshold; return { exists: true, currentTime: currentTime, duration: duration, progress: progress, isPaused: isPaused, isEnded: isEnded, isUnexpectedPause: isUnexpectedPause, volume: videoElement.volume }; } catch (error) { this.log(`检查视频状态出错: ${error.message}`, 'error'); return { exists: false, error: error.message }; } } _startVideoCheck() { if (!this.isRunning) return; this.log(`开始检测视频,检查间隔: ${this.options.checkInterval}秒`, 'debug'); const dynamicCheck = () => { if (!this.isRunning) return; try { this._closePopups(); // 检查是否已完成课程(无视频元素) const videoElement = this._findVideoElement(); if (videoElement) { this.retryCount = 0; // 重置重试计数 this.log('检测到视频元素', 'debug'); if (this.options.autoMute && videoElement.volume > 0) { videoElement.volume = 0; this.log('已将视频设置为静音', 'info'); } const videoState = this._checkVideoState(videoElement); this._updateVideoStatusDisplay(videoState); if (this.options.onVideoDetected) { this.options.onVideoDetected(videoState); } if (videoState.isUnexpectedPause) { this.log('检测到视频被意外暂停,尝试继续播放', 'info'); videoElement.play().catch(err => { this.log(`尝试播放视频失败: ${err.message}`, 'warning'); }); } // 视频播放完成 if (videoState.isEnded && this.lastVideoState && !this.lastVideoState.isEnded) { this.log(`检测到视频播放结束 (进度: ${Math.round(videoState.progress * 100)}%)`, 'info'); if (this.options.onVideoEnded) { this.options.onVideoEnded(videoState); } this._handleVideoEnd(); } this.lastVideoState = videoState; } else { this._updateVideoStatusDisplay({ exists: false }); // 如果没有视频元素,检查是否已完成所有课程 if (this._isCourseComplete()) { this.log('所有课程已完成!', 'success'); this._showNotification('课程学习完成', '恭喜!所有课程已完成', 5000); this.stop(); return; } // 可能页面正在切换,等待一下 if (this.lastVideoState && this.lastVideoState.isEnded) { this.log('等待新课程加载...', 'info'); } this.lastVideoState = null; } } catch (error) { this.log(`视频检测出错: ${error.message}`, 'error'); if (this.options.onError) { this.options.onError(error); } } let nextInterval = this.options.checkInterval * 1000; if (this.lastVideoState && this.lastVideoState.progress > 0.9) { nextInterval = 500; } if (this.isRunning) { this.checkTimer = setTimeout(dynamicCheck, nextInterval); } }; dynamicCheck(); } _isCourseComplete() { // 检查是否有完成标记 for (const selector of this.selectors.completeMarkers) { try { const elements = document.querySelectorAll(selector); for (const element of elements) { if (this._isElementVisible(element) && (element.textContent.includes('完成') || element.textContent.includes('complete'))) { return true; } } } catch (e) {} } // 检查是否显示课程完成信息 const bodyText = document.body.innerText; if (bodyText.includes('恭喜') && bodyText.includes('完成')) { return true; } return false; } _handleVideoEnd() { if (!this.isRunning || this.isSwitching) return; this.isSwitching = true; this.log(`将在 ${this.options.switchDelay} 秒后尝试切换到下一课`, 'info'); if (this.switchTimer) { clearTimeout(this.switchTimer); } this.switchTimer = setTimeout(() => { if (!this.isRunning) { this.isSwitching = false; return; } const switched = this._switchToNextLesson(); if (switched) { this.switchHistory.push({ lesson: this.currentLessonTitle || '未知课时', time: new Date().toISOString() }); if (this.switchHistory.length > 10) { this.switchHistory.shift(); } if (this.options.onLessonSwitched) { this.options.onLessonSwitched(this.currentLessonTitle); } this.log('课程切换成功,等待新课程加载...', 'success'); this.retryCount = 0; this.isSwitching = false; // 延迟后重新提取页面信息 setTimeout(() => { this._extractPageInfo(); }, 3000); } else { this.retryCount++; this.log(`未能找到下一课按钮 (重试 ${this.retryCount}/${this.options.retryTimes})`, 'warning'); this.isSwitching = false; if (this.retryCount < this.options.retryTimes) { // 重试 this.switchTimer = setTimeout(() => { this._handleVideoEnd(); }, 3000); } else { this.log('多次重试后仍未找到下一课按钮,请手动切换', 'error'); this.retryCount = 0; } } }, this.options.switchDelay * 1000); } _switchToNextLesson() { this._closePopups(); // 方法1:通过选择器查找按钮 for (const selector of this.selectors.nextLessonButtons) { try { // 使用 attributeContains 和 textContent 查找 let buttons = []; if (selector.includes(':contains')) { // 处理伪类选择器 const tag = selector.split(':')[0]; const text = selector.match(/:contains\(["']?(.+?)["']?\)/)[1]; const elements = document.querySelectorAll(tag || 'button'); buttons = Array.from(elements).filter(el => el.textContent.includes(text) && this._isElementVisible(el) ); } else { buttons = Array.from(document.querySelectorAll(selector)) .filter(btn => this._isElementVisible(btn)); } if (buttons.length > 0) { this.log(`找到 ${buttons.length} 个下一课按钮 (选择器: ${selector})`, 'info'); // 优先级:文本匹配 const priorityTexts = ['下一课', '下一步', '下一节', '继续学习', '继续', 'Next']; let targetButton = null; for (const text of priorityTexts) { targetButton = buttons.find(btn => btn.textContent.includes(text) && this._isElementVisible(btn) ); if (targetButton) break; } if (!targetButton) { targetButton = buttons[0]; } if (targetButton) { // 多种点击方式 try { targetButton.click(); this.log('已点击下一课按钮', 'success'); return true; } catch (err1) { try { const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); targetButton.dispatchEvent(event); this.log('已触发下一课按钮点击事件', 'success'); return true; } catch (err2) { try { if (targetButton.tagName === 'A' && targetButton.href) { window.location.href = targetButton.href; this.log('已通过链接跳转到下一课', 'success'); return true; } } catch (err3) { this.log(`点击下一课按钮失败: ${err3.message}`, 'error'); } } } } } } catch (error) { this.log(`选择器 "${selector}" 执行出错: ${error.message}`, 'debug'); } } // 方法2:查找包含特定文本的按钮(更通用) const allButtons = document.querySelectorAll('button, a, .btn, [role="button"]'); for (const btn of allButtons) { const text = btn.textContent.trim(); if (this._isElementVisible(btn) && (text.includes('下一课') || text.includes('下一步') || text.includes('下一节') || text.includes('继续学习') || text.includes('继续'))) { try { btn.click(); this.log(`已点击按钮: ${text}`, 'success'); return true; } catch (e) { this.log(`点击按钮失败: ${text}`, 'warning'); } } } // 方法3:查找分页控件 const paginationNext = document.querySelector('.pagination .next, .pager .next, .page-next, [aria-label="下一页"]'); if (paginationNext && this._isElementVisible(paginationNext)) { try { paginationNext.click(); this.log('已点击分页控件的下一页', 'success'); return true; } catch (err) { this.log(`点击分页控件失败: ${err.message}`, 'error'); } } this.log('未找到可点击的下一课按钮', 'error'); return false; } _closePopups() { let closed = false; for (const selector of this.selectors.popupCloseButtons) { try { const closeButtons = document.querySelectorAll(selector); const visibleButtons = Array.from(closeButtons).filter(btn => this._isElementVisible(btn)); if (visibleButtons.length > 0) { visibleButtons.forEach(button => { try { button.click(); closed = true; this.log('已关闭弹窗', 'info'); } catch (error) { this.log(`关闭弹窗失败: ${error.message}`, 'debug'); } }); } } catch (error) { this.log(`关闭弹窗选择器 "${selector}" 执行出错: ${error.message}`, 'debug'); } } // 隐藏模态框 const modalContainers = ['.modal', '.popup', '.dialog', '.layer', '.alert', '.layui-layer']; modalContainers.forEach(selector => { try { const modals = document.querySelectorAll(selector); modals.forEach(modal => { if (this._isElementVisible(modal)) { try { modal.style.display = 'none'; closed = true; this.log(`已隐藏弹窗容器: ${selector}`, 'info'); } catch (error) {} } }); } catch (error) {} }); return closed; } _clearResources() { if (this.checkTimer) { clearTimeout(this.checkTimer); this.checkTimer = null; } if (this.switchTimer) { clearTimeout(this.switchTimer); this.switchTimer = null; } this.lastVideoState = null; } log(message, level = 'info') { if (this.options.debugMode || level !== 'debug') { const prefix = `[华医网自动切换]`; switch (level) { case 'error': console.error(`${prefix} ${message}`); break; case 'warning': console.warn(`${prefix} ${message}`); break; case 'success': console.log(`${prefix} %c${message}`, 'color: #059669'); break; case 'debug': console.debug(`${prefix} %c${message}`, 'color: #6366f1'); break; default: console.log(`${prefix} ${message}`); break; } } this._addLogToPanel(message, level); } _showNotification(title, message, duration = 3000) { if (typeof Notification !== 'undefined' && Notification.permission === 'granted') { new Notification(title, { body: message }); } else if (typeof Notification !== 'undefined' && Notification.permission !== 'denied') { Notification.requestPermission(); } const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; bottom: 20px; left: 20px; background: rgba(31, 41, 55, 0.9); color: white; padding: 10px 15px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 13px; transition: all 0.3s ease; transform: translateY(100px); opacity: 0; `; notification.innerHTML = `
${title}
${message}
`; document.body.appendChild(notification); setTimeout(() => { notification.style.transform = 'translateY(0)'; notification.style.opacity = 1; }, 10); setTimeout(() => { notification.style.transform = 'translateY(100px)'; notification.style.opacity = 0; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, duration); } _notifyStateChange() { if (this.options.onStateChange) { this.options.onStateChange(this.getState()); } } } // 初始化脚本 (function initScript() { const init = () => { setTimeout(() => { const autoNextLesson = new HuaYiAutoNextLesson({ debugMode: false, autoMute: true, switchDelay: 2, checkInterval: 0.8, progressThreshold: 0.95, autoStart: true, retryTimes: 5 }); window.huayiAutoNext = autoNextLesson; }, 1500); }; if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { document.addEventListener('DOMContentLoaded', init); } })();