// ==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);
}
})();