// ==UserScript== // @name 成学课堂自动提交作业(课堂练习专用) // @namespace http://tampermonkey.net/ // @version 2.0 // @description 自动提交作业 // @author cxjy // @match *://*.cx-online.net/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ==================== 全局变量 ==================== let isSubmitting = false; // 是否正在执行提交-返回流程 let panelInjected = false; // 防止重复注入面板 let logPanel = null; // 日志面板元素 let logContainer = null; // 日志内容容器 // ==================== 日志函数 ==================== 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 startSubmission() { if (isSubmitting) return; 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) 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) { // 没有 .lastMark 表示未完成(因为完成的会显示 0分 等) if (!item.querySelector('.lastMark')) { firstIncomplete = item; break; } } if (firstIncomplete) { const nameEl = firstIncomplete.querySelector('.videoItemName'); const exerciseName = nameEl ? nameEl.textContent.trim() : '未知练习'; addLog(`📋 找到未完成练习:${exerciseName}`, 'info'); // 点击进入练习 if (nameEl) { nameEl.click(); addLog(`👉 已点击进入:${exerciseName}`, 'success'); } else { firstIncomplete.click(); addLog(`👉 已点击进入(直接点击item)`, 'success'); } // 点击后等待页面加载,提交按钮会出现,由检测逻辑自动处理 } 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(); }, 3000); } // ==================== 注入控制面板 ==================== 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); // 创建面板HTML const panel = document.createElement('div'); panel.id = 'auto-task-panel'; panel.innerHTML = `