// ==UserScript== // @name 成学课堂自动提交作业(课堂练习专用) // @namespace http://tampermonkey.net/ // @version 2.3 // @description 自动提交作业 + 自动进入未完成课堂练习,循环处理所有练习(带控制面板和日志),连续两次未进入练习自动刷新 // @author YourName // @match *://*.cx-online.net/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // ==================== 阻止 beforeunload 弹窗 ==================== (function blockBeforeUnload() { // 重写 window.addEventListener const originalAdd = window.addEventListener; window.addEventListener = function(type, listener, options) { if (type === 'beforeunload') { console.log('✅ [脚本] 阻止了 beforeunload 事件注册'); return; // 直接忽略 } return originalAdd.call(this, type, listener, options); }; // 处理旧版 IE 的 attachEvent if (window.attachEvent) { const originalAttach = window.attachEvent; window.attachEvent = function(type, listener) { if (type === 'onbeforeunload') { console.log('✅ [脚本] 阻止了 attachEvent beforeunload'); return; } return originalAttach.call(this, type, listener); }; } // 立即清除已有属性 window.onbeforeunload = null; // 定时清除,防止后续脚本直接赋值 setInterval(() => { window.onbeforeunload = null; }, 1000); })(); // ==================== 全局变量 ==================== let isSubmitting = false; // 是否正在执行提交-返回流程 let enteringExercise = false; // 是否正在进入练习(防止重复点击) let panelInjected = false; let logContainer = null; // ========== 新增:连续进入失败计数器 ========== let exerciseEnterFailCount = 0; // ==================== 日志函数 ==================== function addLog(message, type = 'info') { if (!logContainer) return; const time = new Date().toLocaleTimeString(); const entry = document.createElement('div'); entry.className = `log-entry log-${type}`; entry.innerHTML = `[${time}] ${message}`; logContainer.appendChild(entry); logContainer.scrollTop = logContainer.scrollHeight; while (logContainer.children.length > 50) { logContainer.removeChild(logContainer.firstChild); } } // ==================== 等待元素函数 ==================== function waitForElement(selector, onSuccess, onTimeout, timeout = 30000, interval = 500, validator = null) { const startTime = Date.now(); const timer = setInterval(() => { const elements = document.querySelectorAll(selector); if (elements.length > 0) { if (validator) { for (let el of elements) { if (validator(el)) { clearInterval(timer); onSuccess(el); return; } } } else { clearInterval(timer); onSuccess(elements[0]); return; } } if (Date.now() - startTime > timeout) { clearInterval(timer); if (onTimeout) onTimeout(); } }, interval); } // ==================== 自动勾选登录协议复选框 ==================== function autoCheckLoginAgreement() { // 查找包含“登录代表您已同意”文本的 .checkbox 容器 const checkboxContainers = document.querySelectorAll('div.checkbox'); for (let container of checkboxContainers) { // 检查容器内是否有包含目标文本的元素 const textDiv = container.querySelector('div:not(.el-checkbox)'); // 通常文本在单独的div中 if (textDiv && textDiv.textContent.includes('登录代表您已同意')) { const input = container.querySelector('input[type="checkbox"]'); if (input && !input.checked) { input.checked = true; // 触发 change 事件,通知框架更新状态 input.dispatchEvent(new Event('change', { bubbles: true })); addLog('✅ 自动勾选登录协议复选框', 'success'); } break; // 只处理第一个匹配的 } } // 备选:el-checkbox 方式 const checkboxes = document.querySelectorAll('.el-checkbox'); for (let cb of checkboxes) { const textDiv = cb.nextElementSibling; if (textDiv && textDiv.textContent && textDiv.textContent.includes('登录代表您已同意')) { if (!cb.classList.contains('is-checked')) { cb.click(); addLog('✅ 自动勾选同意协议 (el-checkbox)', 'success'); } return; } } // XPath 备选 try { const xpath = "//div[contains(text(),'登录代表您已同意')]/preceding-sibling::label"; const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const label = result.singleNodeValue; if (label && !label.classList.contains('is-checked')) { label.click(); addLog('✅ 自动勾选同意协议 (XPath)', 'success'); return; } } catch (e) {} } // ==================== 自动提交流程 ==================== function startSubmission() { if (isSubmitting) return; // ========== 新增:成功进入练习页面,重置失败计数器 ========== exerciseEnterFailCount = 0; isSubmitting = true; addLog('🚀 开始自动提交作业流程', 'info'); // 步骤1:点击“提交作业”按钮 waitForElement( 'button.el-button--primary.el-button--large span', (span) => { const button = span.closest('button'); if (button) { button.click(); addLog('✅ 步骤1:已点击提交作业按钮', 'success'); // 步骤2:等待确认弹窗的“确定”按钮 waitForElement( '.el-message-box .el-button--primary span', (confirmSpan) => { const confirmBtn = confirmSpan.closest('button'); confirmBtn.click(); addLog('✅ 步骤2:已点击确认弹窗的确定按钮', 'success'); // 步骤3:等待结果弹窗的“返回”按钮 waitForElement( 'footer.el-dialog__footer .el-button:not(.el-button--primary) span', (returnSpan) => { const returnBtn = returnSpan.closest('button'); returnBtn.click(); addLog('✅ 步骤3:已点击结果弹窗的返回按钮', 'success'); // 步骤4:等待页面刷新,点击“课程作业”标签 setTimeout(() => { waitForElement( '.courseMenu_head--item', (courseItem) => { courseItem.click(); addLog('✅ 步骤4:已点击课程作业标签', 'success'); // 返回课程作业页面,重置提交标志 isSubmitting = false; addLog('🔄 返回课程作业页面,准备检查下一个未完成练习', 'info'); // 稍等片刻后检查并处理下一个未完成练习 setTimeout(() => { handleIncompleteExercises(); }, 2000); }, () => { addLog('❌ 步骤4超时:未找到课程作业标签', 'error'); isSubmitting = false; }, 30000, 500, (el) => el.textContent.trim() === '课程作业' ); }, 2000); }, () => { addLog('❌ 步骤3超时:未找到返回按钮', 'error'); isSubmitting = false; }, 30000, 500, (span) => span.textContent.trim() === '返回' ); }, () => { addLog('❌ 步骤2超时:未找到确认弹窗的确定按钮', 'error'); isSubmitting = false; }, 30000, 500, (span) => span.textContent.trim() === '确定' ); } else { addLog('❌ 步骤1错误:无法找到提交作业按钮的父级', 'error'); isSubmitting = false; } }, () => { addLog('❌ 步骤1超时:未找到提交作业按钮', 'error'); isSubmitting = false; }, 30000, 500, (span) => span.textContent.trim() === '提交作业' ); } // ==================== 处理课堂练习页面(课程作业页面) ==================== function handleIncompleteExercises() { // 如果正在提交或正在进入练习,则不处理 if (isSubmitting || enteringExercise) return; // 检测当前页面是否有提交按钮(避免在练习页面误操作) const hasSubmit = !!Array.from(document.querySelectorAll('button.el-button--primary.el-button--large span')).some( span => span.textContent.trim() === '提交作业' ); if (hasSubmit) { // addLog('📝 检测到提交按钮,当前在练习页面,不处理课堂练习', 'info'); return; } // 只查找属于课堂练习的 videoItem(通过父级 data-v-2c3f947c 限定) const exerciseItems = document.querySelectorAll('div[data-v-2c3f947c] .videoItem'); if (!exerciseItems.length) { return; // 可能不是课程作业页面 } let firstIncomplete = null; for (let item of exerciseItems) { if (!item.querySelector('.lastMark')) { // 没有 lastMark 表示未完成 firstIncomplete = item; break; } } if (firstIncomplete) { const nameEl = firstIncomplete.querySelector('.videoItemName'); const exerciseName = nameEl ? nameEl.textContent.trim() : '未知练习'; addLog(`📋 找到未完成练习:${exerciseName}`, 'info'); // 设置进入标志,防止重复点击 enteringExercise = true; // 尝试点击进入练习 if (nameEl) { nameEl.click(); addLog(`👉 已点击进入:${exerciseName}`, 'success'); } else { firstIncomplete.click(); addLog(`👉 已点击进入(直接点击item)`, 'success'); } // 等待页面跳转,3秒后检查是否成功进入(提交按钮出现) setTimeout(() => { // 检查当前页面是否有提交按钮(说明已进入练习页面) const submitNow = !!Array.from(document.querySelectorAll('button.el-button--primary.el-button--large span')).some( span => span.textContent.trim() === '提交作业' ); if (!submitNow && enteringExercise) { // ========== 新增:进入失败,累加计数器 ========== exerciseEnterFailCount++; addLog(`⚠️ 点击后未进入练习页面 (${exerciseEnterFailCount}/2)`, 'warning'); enteringExercise = false; // 连续两次进入失败,强制刷新页面 if (exerciseEnterFailCount >= 2) { addLog('❌ 连续两次进入失败,1秒后强制刷新页面', 'error'); setTimeout(() => { location.reload(); }, 1000); } } else if (submitNow) { // 成功进入,保持 enteringExercise 为 true,等待提交流程启动 // 提交流程启动时会重置计数器,此处无需额外操作 } }, 3000); // 5秒后强制重置 enteringExercise,防止卡死(但不增加失败计数,因为3秒已处理) setTimeout(() => { if (enteringExercise) { enteringExercise = false; addLog('⏰ 进入练习超时保护,重置标志', 'info'); } }, 5000); } else { addLog('🎉 所有课堂练习已完成,流程结束', 'success'); } } // ==================== 主检测循环 ==================== function startMainLoop() { setInterval(() => { // 检测是否有提交作业按钮(说明当前在练习页面) const submitSpan = Array.from(document.querySelectorAll('button.el-button--primary.el-button--large span')).find( span => span.textContent.trim() === '提交作业' ); if (submitSpan) { if (!isSubmitting) { addLog('🔍 检测到提交作业按钮,启动自动提交流程', 'info'); startSubmission(); } return; // 有提交按钮,不执行课程作业页面逻辑 } // 没有提交按钮,可能是课程作业页面,检查未完成练习 handleIncompleteExercises(); }, 2000); // 每2秒检测一次 } // ==================== 注入控制面板 ==================== function injectPanel() { if (panelInjected || document.getElementById('auto-task-panel')) return; panelInjected = true; const style = document.createElement('style'); style.textContent = ` #auto-task-panel { position: fixed; top: 20px; right: 20px; width: 380px; background: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.2); z-index: 10000; font-family: 'Microsoft YaHei', sans-serif; border: 1px solid #e0e0e0; overflow: hidden; resize: both; min-width: 300px; min-height: 200px; } #auto-task-panel .panel-header { background: #1E4EE7; color: white; padding: 10px 15px; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none; } #auto-task-panel .panel-title { font-weight: 600; font-size: 14px; } #auto-task-panel .panel-controls button { background: transparent; border: none; color: white; cursor: pointer; font-size: 16px; margin-left: 5px; padding: 0 5px; } #auto-task-panel .panel-controls button:hover { background: rgba(255,255,255,0.2); } #auto-task-panel .panel-body { padding: 15px; max-height: 400px; overflow-y: auto; background: #f9f9f9; } #auto-task-panel .status-item { margin-bottom: 8px; font-size: 13px; } #auto-task-panel .log-container { background: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 8px; height: 200px; overflow-y: auto; font-size: 12px; margin-top: 10px; } #auto-task-panel .log-entry { padding: 3px 0; border-bottom: 1px solid #f0f0f0; } #auto-task-panel .log-time { color: #999; margin-right: 5px; } #auto-task-panel .log-info { color: #1890ff; } #auto-task-panel .log-success { color: #00a854; } #auto-task-panel .log-warning { color: #fa8c16; } #auto-task-panel .log-error { color: #f5222d; } #auto-task-panel .footer { margin-top: 10px; font-size: 12px; color: #666; text-align: center; } `; document.head.appendChild(style); const panel = document.createElement('div'); panel.id = 'auto-task-panel'; panel.innerHTML = `
成学课堂自动提交(课堂练习专用)
🔄 当前状态:等待检测...
📊 已完成练习:0
`; document.body.appendChild(panel); // 拖拽功能 const header = document.getElementById('panel-header'); let isDragging = false, offsetX, offsetY; header.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; panel.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; let left = e.clientX - offsetX; let top = e.clientY - offsetY; left = Math.max(0, Math.min(left, window.innerWidth - panel.offsetWidth)); top = Math.max(0, Math.min(top, window.innerHeight - panel.offsetHeight)); panel.style.left = left + 'px'; panel.style.top = top + 'px'; panel.style.right = 'auto'; }); document.addEventListener('mouseup', () => { isDragging = false; panel.style.cursor = ''; }); document.getElementById('panel-minimize').addEventListener('click', () => { const body = document.getElementById('panel-body'); body.style.display = body.style.display === 'none' ? 'block' : 'none'; }); document.getElementById('panel-close').addEventListener('click', () => { panel.style.display = 'none'; }); logContainer = document.getElementById('log-container'); addLog('控制面板已加载,开始监控页面...', 'info'); } // ==================== 初始化 ==================== function init() { injectPanel(); startMainLoop(); setTimeout(() => { const submitSpan = Array.from(document.querySelectorAll('button.el-button--primary.el-button--large span')).find( span => span.textContent.trim() === '提交作业' ); if (submitSpan) { addLog('页面加载后检测到提交作业按钮,启动流程', 'info'); startSubmission(); } else { handleIncompleteExercises(); } // 自动勾选协议 autoCheckLoginAgreement(); }, 2000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } setTimeout(init, 1000); })();