// ==UserScript== // @name 山东教育教师网 - 自动学习脚本 // @namespace https://github.com/your-repo/shandong-edu-auto-study // @version 3.0.0 // @description 在山东教育教师网学习网课时,完成学习后自动跳转到下一课程 // @author Your Name // @match *://*.qlteacher.com/* // @match *://*.sdedu.gov.cn/* // @match *://*.sdteacher.com/* // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // ==================== 配置区域 ==================== const CONFIG = { debug: true, jumpDelay: 2000, // 跳转延迟(毫秒) checkInterval: 1000, // 检查间隔(毫秒) selectors: { countdown: '.count-down', chapterItem: '.ant-list-item', videoPlayer: 'video', checkIcon: '.anticon-check-circle', ellipsis: 'ELLIPSIS', }, logPrefix: '[山东教育自动学习]', }; // ==================== 样式 ==================== function addStyles() { var css = ` #auto-study-panel { position: fixed; top: 10px; right: 10px; width: 320px; background: #fff; border: 2px solid #1890ff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 13px; } #auto-study-panel.collapsed { width: 120px; height: 40px; overflow: hidden; } #auto-study-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 10px 15px; border-radius: 6px 6px 0 0; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; } #auto-study-panel.collapsed #auto-study-header { border-radius: 6px; } #auto-study-title { font-weight: bold; font-size: 14px; } #auto-study-toggle { background: rgba(255,255,255,0.2); border: none; color: #fff; padding: 2px 8px; border-radius: 3px; cursor: pointer; font-size: 12px; } #auto-study-toggle:hover { background: rgba(255,255,255,0.3); } #auto-study-content { padding: 12px 15px; max-height: 400px; overflow-y: auto; } .status-item { margin-bottom: 10px; padding: 8px; background: #f5f5f5; border-radius: 4px; } .status-label { color: #666; font-size: 11px; margin-bottom: 3px; } .status-value { color: #333; font-size: 13px; font-weight: 500; word-break: break-all; } .status-value.success { color: #52c41a; } .status-value.warning { color: #faad14; } .status-value.error { color: #f5222d; } `; var style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); log('✓ 样式已添加'); } // ==================== UI 面板 ==================== var panelElement = null; function createPanel() { if (panelElement) return; panelElement = document.createElement('div'); panelElement.id = 'auto-study-panel'; panelElement.innerHTML = `
🎓 自动学习助手
📖 当前章节
-
⏭️ 下一跳转
-
⏱️ 倒计时
-
`; document.body.appendChild(panelElement); // 绑定事件 document.getElementById('auto-study-toggle').addEventListener('click', function() { panelElement.classList.toggle('collapsed'); this.textContent = panelElement.classList.contains('collapsed') ? '展开' : '收起'; }); // 添加拖动功能 makeDraggable(panelElement, document.getElementById('auto-study-header')); log('✓ UI 面板已创建(可拖动)'); } function makeDraggable(element, handle) { var isDragging = false; var currentX = 0; var currentY = 0; var initialX = 0; var initialY = 0; handle.addEventListener('mousedown', dragStart); function dragStart(e) { // 如果点击的是按钮,不触发拖动 if (e.target.tagName === 'BUTTON') return; isDragging = true; initialX = e.clientX - currentX; initialY = e.clientY - currentY; document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); } function drag(e) { if (!isDragging) return; e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; // 限制在窗口内 var maxX = window.innerWidth - element.offsetWidth; var maxY = window.innerHeight - element.offsetHeight; currentX = Math.max(0, Math.min(currentX, maxX)); currentY = Math.max(0, Math.min(currentY, maxY)); element.style.transform = 'translate(' + currentX + 'px, ' + currentY + 'px)'; } function dragEnd() { isDragging = false; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', dragEnd); } } function updateUI() { if (!panelElement) return; // 更新当前章节 var statusChapter = document.getElementById('status-chapter'); var incompleteChapter = findFirstIncompleteChapter(); if (incompleteChapter) { statusChapter.textContent = '[' + incompleteChapter.index + '] ' + incompleteChapter.text.substring(0, 25); } else { statusChapter.textContent = '所有章节已完成'; statusChapter.className = 'status-value success'; } // 更新下一跳转章节 var statusNext = document.getElementById('status-next'); var nextChapter = findNextIncompleteChapter(); if (nextChapter) { statusNext.textContent = '[' + nextChapter.index + '] ' + nextChapter.text.substring(0, 25); statusNext.className = 'status-value warning'; } else { statusNext.textContent = '无'; statusNext.className = 'status-value success'; } // 更新倒计时 var statusCountdown = document.getElementById('status-countdown'); var countdownText = getCountdownText(); if (countdownText) { statusCountdown.textContent = countdownText; if (countdownText.includes('已完成')) { statusCountdown.className = 'status-value success'; } else { statusCountdown.className = 'status-value'; } } else { statusCountdown.textContent = '未找到'; statusCountdown.className = 'status-value warning'; } } // ==================== 工具函数 ==================== function log(...args) { console.log(CONFIG.logPrefix, ...args); } function warn(...args) { console.warn(CONFIG.logPrefix, ...args); } function error(...args) { console.error(CONFIG.logPrefix, ...args); } function debug(...args) { if (CONFIG.debug) { console.debug(CONFIG.logPrefix, ...args); } } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 定时更新UI var uiUpdateTimer = null; function startUIUpdate() { if (uiUpdateTimer) return; uiUpdateTimer = setInterval(function() { updateUI(); }, 1000); // 每秒更新一次UI } // ==================== 核心功能 ==================== /** * 检查章节是否有完成图标 */ function hasCheckIcon(chapter) { return chapter.querySelector(CONFIG.selectors.checkIcon) !== null; } /** * 检查当前页面是否有视频 */ function hasVideo() { return document.querySelector(CONFIG.selectors.videoPlayer) !== null; } /** * 获取倒计时文本 */ function getCountdownText() { var countdown = document.querySelector(CONFIG.selectors.countdown); return countdown ? countdown.textContent.trim() : null; } /** * 检查当前课程是否完成 */ function isCourseComplete() { var text = getCountdownText(); if (!text) return false; return text.includes('已完成') || text.includes('已学完') || text === '0:00'; } /** * 查找第一个未完成的章节 */ function findFirstIncompleteChapter() { var allChapters = document.querySelectorAll(CONFIG.selectors.chapterItem); for (var i = 0; i < allChapters.length; i++) { var chapter = allChapters[i]; if (!hasCheckIcon(chapter)) { return { index: i, element: chapter, text: chapter.textContent.trim() }; } } return null; // 所有章节都已完成 } /** * 查找下一个未完成的章节(跳过当前章节) */ function findNextIncompleteChapter() { var allChapters = document.querySelectorAll(CONFIG.selectors.chapterItem); var currentChapterId = getCurrentChapterId(); // 如果无法获取当前章节ID,返回第一个未完成的 if (currentChapterId === null) { return findFirstIncompleteChapter(); } // 查找当前章节之后的第一个未完成章节 for (var i = 0; i < allChapters.length; i++) { var chapter = allChapters[i]; if (!hasCheckIcon(chapter) && i > currentChapterId) { return { index: i, element: chapter, text: chapter.textContent.trim() }; } } // 如果后面没有了,返回第一个未完成的(循环) for (var j = 0; j < allChapters.length; j++) { var chapter2 = allChapters[j]; if (!hasCheckIcon(chapter2)) { return { index: j, element: chapter2, text: chapter2.textContent.trim() }; } } return null; // 所有章节都已完成 } /** * 获取当前章节的ID(通过URL) */ function getCurrentChapterId() { var url = window.location.href; var urlParts = url.split('/'); var currentId = urlParts[urlParts.length - 1]; // 在章节列表中查找匹配的 var allChapters = document.querySelectorAll(CONFIG.selectors.chapterItem); for (var i = 0; i < allChapters.length; i++) { var chapter = allChapters[i]; var ellipsis = chapter.querySelector(CONFIG.selectors.ellipsis); if (ellipsis) { // 通过点击后的URL来判断(这里简化处理,返回索引) // 实际上可以通过Angular的路由数据获取 } } // 简化:通过倒计时状态判断 // 如果有倒计时且不是"已完成",说明在当前章节 var countdown = getCountdownText(); if (countdown && !countdown.includes('已完成')) { // 找到第一个未完成的章节(假设就是当前章节) for (var k = 0; k < allChapters.length; k++) { if (!hasCheckIcon(allChapters[k])) { return k; } } } return null; } /** * 跳转到指定章节 */ function jumpToChapter(chapterInfo) { if (!chapterInfo || !chapterInfo.element) { warn('无法跳转:章节信息无效'); return false; } var ellipsis = chapterInfo.element.querySelector(CONFIG.selectors.ellipsis); if (!ellipsis) { warn('无法跳转:找不到ELLIPSIS元素'); return false; } log('跳转到章节[' + chapterInfo.index + ']:', chapterInfo.text.substring(0, 30)); ellipsis.click(); return true; } /** * 检查并跳转(核心逻辑) */ async function checkAndJump() { debug('========== 检查开始 =========='); // 1. 检查是否所有章节都已完成 var allChapters = document.querySelectorAll(CONFIG.selectors.chapterItem); var allCompleted = true; for (var i = 0; i < allChapters.length; i++) { if (!hasCheckIcon(allChapters[i])) { allCompleted = false; break; } } if (allCompleted) { log('🎉 所有章节都已完成!'); stopScript(); return; } // 2. 检查当前章节是否有视频 if (!hasVideo()) { log('⚠ 当前章节没有视频,直接跳转'); var incompleteChapter = findFirstIncompleteChapter(); if (incompleteChapter) { await sleep(CONFIG.jumpDelay); jumpToChapter(incompleteChapter); } return; } // 3. 检查倒计时是否完成 if (isCourseComplete()) { log('✓ 当前课程已完成,查找下一章节'); var incompleteChapter = findFirstIncompleteChapter(); if (incompleteChapter) { await sleep(CONFIG.jumpDelay); jumpToChapter(incompleteChapter); } else { log('🎉 所有章节都已完成!'); stopScript(); } } else { var countdownText = getCountdownText(); debug('当前倒计时:', countdownText); } debug('========== 检查结束 =========='); } // ==================== 主循环 ==================== let mainTimer = null; let isRunning = false; /** * 主循环 */ async function mainLoop() { if (isRunning) { debug('上一轮检查尚未完成,跳过'); return; } isRunning = true; try { await checkAndJump(); } catch (e) { error('主循环出错:', e); } finally { isRunning = false; } } /** * 启动脚本 */ function startScript() { log('脚本正在启动...'); // 清除已存在的定时器 if (mainTimer) { clearInterval(mainTimer); mainTimer = null; } // 立即执行一次检查 mainLoop(); // 设置定时检查 mainTimer = setInterval(mainLoop, CONFIG.checkInterval); // 启动UI更新 startUIUpdate(); log('✓ 脚本已启动,开始监控...'); } /** * 停止脚本 */ function stopScript() { if (mainTimer) { clearInterval(mainTimer); mainTimer = null; } log('脚本已停止'); } // ==================== 控制台命令 ==================== const safeWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; safeWindow.checkStatus = function() { log('========== 当前状态 =========='); log('URL:', location.href); log('有视频:', hasVideo()); log('倒计时:', getCountdownText()); log('课程完成:', isCourseComplete()); var incomplete = findFirstIncompleteChapter(); if (incomplete) { log('第一个未完成章节[' + incomplete.index + ']:', incomplete.text.substring(0, 30)); } else { log('所有章节都已完成'); } log('=============================='); }; safeWindow.stopScript = function() { stopScript(); }; safeWindow.startScript = function() { startScript(); }; // ==================== 初始化 ==================== // 等待页面加载完成后创建UI if (document.readyState === 'complete') { addStyles(); createPanel(); startScript(); } else { window.addEventListener('load', function() { addStyles(); createPanel(); startScript(); }); } })();