// ==UserScript==
// @name 华医网课程自动切换工具(增强优化版)
// @namespace https://github.com/user/auto-next-lesson-tool
// @version 3.1.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-end
// ==/UserScript==
class HuaYiAutoNextLesson {
constructor(options = {}) {
this.options = {
switchDelay: 2, // 切换延迟时间(秒)
checkInterval: 0.8, // 优化检测频率
progressThreshold: 0.95, // 进度阈值,95%视为可切换
debugMode: false,
autoMute: true,
showControlPanel: true,
panelPosition: 'top-right',
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.selectors = {
videoElements: [
'video',
'.video-js video',
'.vjs-tech',
'#video-player video',
'.video-container video',
'.player-container video',
'[id*="video"] video',
'[class*="video"] video'
],
nextLessonButtons: [
'.next-btn',
'.next-step',
'.next-lesson',
'.next-button',
'[id*="next"]',
'[class*="next"]',
'[onclick*="next"]',
'[href*="next"]',
'button:contains("下一步")',
'button:contains("下一课")',
'button:contains("继续学习")',
'a:contains("下一步")',
'a:contains("下一课")',
'a:contains("继续学习")',
'[id*="nextLesson"]',
'[class*="nextLesson"]',
'.btn-next',
'.step-next',
'.lesson-next',
'.btn-primary:contains("下一节")', // 新增常见按钮样式
'.btn-success:contains("继续")',
'[data-action="next"]',
'.pagination-next',
'.nav-next'
],
popupCloseButtons: [
'.close-btn',
'.close',
'[data-dismiss="modal"]',
'.modal-close',
'.popup-close',
'[id*="close"]',
'[class*="close"]',
'button:contains("确定")',
'button:contains("知道了")',
'button:contains("关闭")',
'button:contains("继续")',
'.layui-layer-close', // 新增常见弹窗关闭按钮
'.ui-dialog-close'
],
progressBars: [
'.progress-bar',
'.progress',
'.vjs-progress-bar',
'.vjs-play-progress',
'[class*="progress"]',
'[id*="progress"]'
],
courseTitle: [
'.course-title',
'.course-name',
'h1',
'h2',
'[id*="course"]',
'[class*="course"]'
],
lessonTitle: [
'.lesson-title',
'.lesson-name',
'h3',
'h4',
'[id*="lesson"]',
'[class*="lesson"]'
]
};
this.log('华医网课程自动切换工具已初始化', 'info');
this._detectCurrentPage();
if (this.options.showControlPanel) {
this._createControlPanel();
}
}
// 检测当前页面类型
_detectCurrentPage() {
const url = window.location.href.toLowerCase();
if (url.includes('/course/') || url.includes('/lesson/')) {
this.log('检测到课程播放页面', 'info');
this._extractPageInfo();
} else if (url.includes('/course/list') || url.includes('/courses')) {
this.log('检测到课程列表页面', 'info');
} else if (url.includes('/exam/') || url.includes('/test/')) {
this.log('检测到考试页面', 'info');
} 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();
}
// 创建控制面板
_createControlPanel() {
this.controlPanel = document.createElement('div');
this.controlPanel.id = 'huayi-auto-next-control-panel';
this.controlPanel.className = 'huayi-control-panel';
// 优化面板样式,更简洁紧凑
const panelStyles = `
position: fixed;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 10px;
width: 280px;
z-index: 99999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 12px;
transition: all 0.3s ease;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
`;
let positionStyles = '';
switch (this.options.panelPosition) {
case 'top-left':
positionStyles = 'top: 20px; left: 20px;';
break;
case 'bottom-left':
positionStyles = 'bottom: 20px; left: 20px;';
break;
case 'bottom-right':
positionStyles = 'bottom: 20px; right: 20px;';
break;
case 'top-right':
default:
positionStyles = 'top: 20px; right: 20px;';
break;
}
this.controlPanel.style.cssText = panelStyles + positionStyles;
// 面板内容
this.controlPanel.innerHTML = `
状态:
未启动
视频检测:
未检测
当前课程:
${this.currentCourseTitle || '未知'}
`;
document.body.appendChild(this.controlPanel);
this._bindControlPanelEvents();
this._updateControlPanelState();
}
// 绑定控制面板事件
_bindControlPanelEvents() {
if (!this.controlPanel) return;
// 启动按钮
document.getElementById('huayi-btn-start')?.addEventListener('click', () => {
this.start();
this._updateControlPanelState();
});
// 停止按钮
document.getElementById('huayi-btn-stop')?.addEventListener('click', () => {
this.stop();
this._updateControlPanelState();
});
// 静音按钮
const muteBtn = document.getElementById('huayi-btn-mute');
muteBtn?.addEventListener('click', () => {
this.options.autoMute = !this.options.autoMute;
muteBtn.textContent = this.options.autoMute ? '静音: 开' : '静音: 关';
this.updateOptions({ autoMute: this.options.autoMute });
this.log(`${this.options.autoMute ? '开启' : '关闭'}自动静音`, 'info');
});
// 调试按钮
const debugBtn = document.getElementById('huayi-btn-debug');
debugBtn?.addEventListener('click', () => {
this.options.debugMode = !this.options.debugMode;
debugBtn.textContent = this.options.debugMode ? '调试: 开' : '调试: 关';
this.updateOptions({ debugMode: this.options.debugMode });
this.log(`${this.options.debugMode ? '开启' : '关闭'}调试模式`, 'info');
});
// 切换延迟输入
document.getElementById('huayi-switch-delay')?.addEventListener('change', (e) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value >= 0) {
this.updateOptions({ switchDelay: value });
this.log(`切换延迟已设置为 ${value} 秒`, 'info');
}
});
// 进度阈值输入
document.getElementById('huayi-progress-threshold')?.addEventListener('change', (e) => {
const value = parseFloat(e.target.value) / 100;
if (!isNaN(value) && value >= 0.8 && value <= 1) {
this.updateOptions({ progressThreshold: value });
this.log(`进度阈值已设置为 ${value * 100}%`, 'info');
}
});
// 检查间隔输入
document.getElementById('huayi-check-interval')?.addEventListener('change', (e) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value > 0) {
this.updateOptions({ checkInterval: value });
this.log(`检查间隔已设置为 ${value} 秒`, 'info');
}
});
// 清空日志按钮
document.getElementById('huayi-btn-clear-logs')?.addEventListener('click', () => {
this._clearLogs();
});
// 最小化按钮
const minimizeBtn = document.getElementById('huayi-btn-minimize');
minimizeBtn?.addEventListener('click', () => {
this._togglePanelMinimize();
});
// 优化面板拖动功能
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';
// 添加视觉反馈
this.controlPanel.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.2)';
this.controlPanel.style.transform = 'scale(1.02)';
});
// 拖动过程 - 使用requestAnimationFrame优化性能
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 = '';
// 恢复样式
this.controlPanel.style.boxShadow = '';
this.controlPanel.style.transform = '';
}
};
// 绑定事件
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';
minimizeBtn.textContent = '▼';
} else {
content.style.display = 'none';
minimizeBtn.textContent = '▲';
}
}
// 更新控制面板状态
_updateControlPanelState() {
if (!this.controlPanel) return;
// 更新状态指示器
const statusIndicator = document.getElementById('huayi-status-indicator');
const statusValue = document.getElementById('huayi-status-value');
if (statusIndicator && statusValue) {
if (this.isRunning) {
statusIndicator.style.backgroundColor = '#10b981'; // 绿色
statusValue.textContent = '运行中';
statusValue.style.color = '#10b981';
} else {
statusIndicator.style.backgroundColor = '#6b7280'; // 灰色
statusValue.textContent = '已停止';
statusValue.style.color = '#6b7280';
}
}
// 更新按钮状态
const startBtn = document.getElementById('huayi-btn-start');
const stopBtn = document.getElementById('huayi-btn-stop');
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;
if (videoState.exists) {
const progressPercent = Math.round(videoState.progress * 100);
videoStatusElement.textContent = `播放中 ${progressPercent}%`;
videoStatusElement.style.color = videoState.isPaused ? '#f59e0b' : '#10b981';
} else {
videoStatusElement.textContent = '未检测';
videoStatusElement.style.color = '#9ca3af';
}
}
// 添加日志到面板
_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;
case 'info':
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.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._clearTimers();
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._clearTimers();
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);
if (elements.length > 0) {
this.log(`使用选择器 "${selector}" 找到 ${elements.length} 个视频元素`, 'debug');
// 返回第一个可见的视频元素
for (const element of elements) {
if (this._isElementVisible(element)) {
return element;
}
}
}
} catch (error) {
this.log(`选择器 "${selector}" 执行出错: ${error.message}`, 'debug');
}
}
// 如果没有找到视频元素,尝试查找其他可能的视频播放元素
const videoContainers = document.querySelectorAll('.video-container, .player-container, .video-player');
if (videoContainers.length > 0) {
this.log(`找到 ${videoContainers.length} 个视频容器`, 'debug');
// 尝试在容器内查找视频元素
for (const container of videoContainers) {
const containerVideo = container.querySelector('video');
if (containerVideo && this._isElementVisible(containerVideo)) {
this.log('在视频容器内找到视频元素', 'debug');
return containerVideo;
}
}
}
return null;
}
// 检查元素是否可见
_isElementVisible(element) {
// 检查元素是否在DOM中
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;
}
// 检查元素是否在视口中(放宽条件,只要部分可见即可)
const isInViewport = (
rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
rect.right > 0
);
return isInViewport;
}
// 检查视频状态 - 优化版本
_checkVideoState(videoElement) {
try {
// 检查视频元素的基本属性
const currentTime = videoElement.currentTime || 0;
const duration = videoElement.duration || 0;
const progress = duration > 0 ? currentTime / duration : 0;
const isPaused = videoElement.paused;
// 优化结束判断逻辑:结合视频自带的ended属性和进度阈值
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 checkVideo = () => {
if (!this.isRunning) return;
try {
// 检查并关闭弹窗
this._closePopups();
// 查找视频元素
const videoElement = this._findVideoElement();
if (videoElement) {
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');
try {
videoElement.play().catch(err => {
this.log(`尝试播放视频失败: ${err.message}`, 'warning');
});
} catch (playErr) {
this.log(`播放视频时出错: ${playErr.message}`, 'error');
}
}
// 检查视频是否结束(且状态有变化)
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 });
this.lastVideoState = null;
}
} catch (error) {
this.log(`视频检测出错: ${error.message}`, 'error');
if (this.options.onError) {
this.options.onError(error);
}
}
// 设置下一次检查
if (this.isRunning) {
this.checkTimer = setTimeout(checkVideo, this.options.checkInterval * 1000);
}
};
// 立即执行一次检查
checkVideo();
}
// 处理视频结束
_handleVideoEnd() {
if (!this.isRunning) return;
this.log(`将在 ${this.options.switchDelay} 秒后尝试切换到下一课`, 'info');
// 清除可能存在的切换计时器
if (this.switchTimer) {
clearTimeout(this.switchTimer);
}
// 设置切换计时器
this.switchTimer = setTimeout(() => {
if (!this.isRunning) 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);
}
// 切换后重新加载页面信息
setTimeout(() => {
this._extractPageInfo();
}, 2000);
} else {
this.log('未能找到下一课按钮,将继续尝试', 'warning');
// 继续尝试,每5秒一次
this.switchTimer = setTimeout(() => {
this._handleVideoEnd();
}, 5000);
}
}, this.options.switchDelay * 1000);
}
// 优化切换到下一课的逻辑
_switchToNextLesson() {
// 再次关闭可能弹出的弹窗
this._closePopups();
// 尝试所有可能的下一课按钮选择器
for (const selector of this.selectors.nextLessonButtons) {
try {
// 尝试直接选择
let buttons = document.querySelectorAll(selector);
// 过滤可见的按钮
buttons = Array.from(buttons).filter(btn => this._isElementVisible(btn));
if (buttons.length > 0) {
this.log(`找到 ${buttons.length} 个可能的下一课按钮 (选择器: ${selector})`, 'info');
// 优先点击包含特定文本的按钮
const priorityTexts = ['下一课', '下一步', '继续学习', '下一节'];
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) {
this.log('尝试点击下一课按钮', 'info');
// 尝试多种点击方式
try {
// 方式1: 直接点击
targetButton.click();
this.log('已点击下一课按钮', 'success');
return true;
} catch (clickErr1) {
try {
// 方式2: 触发click事件
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
targetButton.dispatchEvent(event);
this.log('已触发下一课按钮点击事件', 'success');
return true;
} catch (clickErr2) {
try {
// 方式3: 如果是链接,直接跳转
if (targetButton.tagName === 'A' && targetButton.href) {
window.location.href = targetButton.href;
this.log('已通过链接跳转到下一课', 'success');
return true;
}
} catch (clickErr3) {
this.log(`点击下一课按钮失败: ${clickErr3.message}`, 'error');
}
}
}
}
}
} catch (error) {
this.log(`选择器 "${selector}" 执行出错: ${error.message}`, 'debug');
}
}
// 如果没有找到按钮,尝试查找分页控件
this.log('尝试查找分页控件', 'debug');
const paginationNext = document.querySelector('.pagination .next, .pager .next, .page-next');
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) {
this.log(`找到 ${visibleButtons.length} 个弹窗关闭按钮 (选择器: ${selector})`, 'debug');
// 点击每个可见的关闭按钮
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'];
modalContainers.forEach(selector => {
try {
const modals = document.querySelectorAll(`${selector}:not([style*="display: none"])`,
`${selector}:not([hidden])`);
modals.forEach(modal => {
try {
// 尝试直接隐藏
modal.style.display = 'none';
closed = true;
this.log(`已隐藏弹窗容器: ${selector}`, 'info');
} catch (error) {
this.log(`隐藏弹窗容器失败: ${error.message}`, 'debug');
}
});
} catch (error) {
this.log(`处理弹窗容器 "${selector}" 出错: ${error.message}`, 'debug');
}
});
return closed;
}
// 清除计时器
_clearTimers() {
if (this.checkTimer) {
clearTimeout(this.checkTimer);
this.checkTimer = null;
}
if (this.switchTimer) {
clearTimeout(this.switchTimer);
this.switchTimer = 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;
case 'info':
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: 99999;
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(() => {
document.body.removeChild(notification);
}, 300);
}, duration);
}
// 通知状态变化
_notifyStateChange() {
if (this.options.onStateChange) {
this.options.onStateChange(this.getState());
}
}
}
// 初始化工具
document.addEventListener('DOMContentLoaded', () => {
// 等待页面完全加载
setTimeout(() => {
const autoNextLesson = new HuaYiAutoNextLesson({
debugMode: false,
autoMute: true,
switchDelay: 2,
checkInterval: 0.8,
progressThreshold: 0.95
});
// 暴露到window方便调试
window.huayiAutoNext = autoNextLesson;
// 自动启动(可根据需要改为手动启动)
autoNextLesson.start();
}, 1000);
});