// ==UserScript== // @name 中南林一键自动教评--电脑端(上海商鼎软件平台) // @namespace http://tampermonkey.net/ // @version 1.1 // @description 自动填写教学评价分数,依赖页面自然刷新状态,支持拖动面板和网络检测 // @author IKUN-91-张 // @match https://jxzlpt.csuft.edu.cn/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 全局状态变量 let isRunning = false; let panelCreated = false; let currentEvaluationId = null; let retryTimer = null; let statusUpdateTimer = null; let dialogDetectionInterval = null; let submissionConfirmTimer = null; let isDragging = false; let dragOffsetX = 0, dragOffsetY = 0; let panelX = 30, panelY = 30; let panelWidth = 0, panelHeight = 0; let networkErrorCount = 0; // 网络错误计数器 const MAX_NETWORK_ERRORS = 3; // 最大允许网络错误次数 // 缓存常用DOM元素 let panel, statusElem, counterElem; // 主初始化函数 function mainInit() { if (window.location.href.includes('/dfpj')) { initApp(); } bindRouteEvents(); setupNetworkMonitoring(); // 设置网络监控 } // 初始化应用 function initApp() { if (!window.location.href.includes('/dfpj')) return; if (panelCreated) return; // 尝试从localStorage加载面板位置 const savedPosition = localStorage.getItem('autoEvalPanelPosition'); if (savedPosition) { const pos = JSON.parse(savedPosition); panelX = pos.x; panelY = pos.y; } // 等待页面元素加载完成 const checkElements = () => { const evaluateBtn = document.querySelector('.btn_theme'); if (evaluateBtn) { createControlPanel(); } else { setTimeout(checkElements, 500); } }; checkElements(); } // 设置网络监控 function setupNetworkMonitoring() { // 监听网络状态变化 window.addEventListener('online', handleNetworkOnline); window.addEventListener('offline', handleNetworkOffline); // 初始检查 if (!navigator.onLine) { handleNetworkOffline(); } } // 处理网络在线 function handleNetworkOnline() { if (statusElem) { statusElem.textContent = '状态: 网络已恢复'; statusElem.style.background = '#e8f5e9'; // 如果是网络错误导致的暂停,显示提示 setTimeout(() => { if (statusElem) { statusElem.textContent = '状态: 网络已恢复 - 请手动启动评价'; } }, 2000); } } // 处理网络离线 function handleNetworkOffline() { if (isRunning) { pauseEvaluation('网络已断开'); } if (statusElem) { statusElem.textContent = '状态: 网络已断开 - 请检查网络连接'; statusElem.style.background = '#ffebee'; } } // 路由变化处理函数 function handleRouteChange() { panelCreated = false; if (panel) { panel.remove(); panel = null; } initApp(); if (isRunning) { tryClickFirstEvaluateBtn(); } } // 绑定路由事件 function bindRouteEvents() { window.addEventListener('popstate', handleRouteChange); const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function() { const result = originalPushState.apply(history, arguments); handleRouteChange(); return result; }; history.replaceState = function() { const result = originalReplaceState.apply(history, arguments); handleRouteChange(); return result; }; } // 创建控制面板 function createControlPanel() { if (panelCreated) return; panelCreated = true; panel = document.createElement('div'); panel.id = 'auto-eval-panel'; panel.style.cssText = ` position: fixed; left: ${panelX}px; top: ${panelY}px; z-index: 9999; background: #fff; border: 2px solid #4CAF50; padding: 15px 25px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); font-family: Arial, sans-serif; display: flex; flex-direction: column; gap: 10px; min-width: 280px; cursor: default; user-select: none; `; // 添加标题栏 const title = document.createElement('div'); title.textContent = '教学评价助手 v1.0'; title.style.cssText = ` font-weight: bold; font-size: 18px; color: #2196F3; text-align: center; margin-bottom: 10px; padding: 5px 10px; background: #f5f5f5; border-radius: 6px; cursor: move; user-select: none; `; panel.appendChild(title); const btnContainer = document.createElement('div'); btnContainer.style.display = 'flex'; btnContainer.style.gap = '10px'; btnContainer.style.justifyContent = 'center'; const btnStyle = ` padding: 8px 15px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: bold; transition: all 0.3s; min-width: 120px; user-select: none; `; const startBtn = document.createElement('button'); startBtn.textContent = '▶ 启动自动评价'; startBtn.style.cssText = btnStyle + 'background-color: #4CAF50; color: white;'; const pauseBtn = document.createElement('button'); pauseBtn.textContent = '⏸ 暂停自动评价'; pauseBtn.style.cssText = btnStyle + 'background-color: #f44336; color: white;'; // 添加网络状态指示器 const networkIndicator = document.createElement('div'); networkIndicator.id = 'network-indicator'; networkIndicator.style.cssText = ` width: 12px; height: 12px; border-radius: 50%; background-color: ${navigator.onLine ? '#4CAF50' : '#f44336'}; display: inline-block; margin-left: 8px; vertical-align: middle; `; title.appendChild(networkIndicator); statusElem = document.createElement('div'); statusElem.id = 'auto-eval-status'; statusElem.textContent = navigator.onLine ? '状态: 等待启动' : '状态: 网络已断开'; statusElem.style.cssText = ` margin-top: 5px; padding: 8px; background: ${navigator.onLine ? '#f9f9f9' : '#ffebee'}; border-radius: 4px; text-align: center; font-size: 13px; border: 1px solid #eee; min-height: 20px; user-select: none; `; // 添加状态计数器 counterElem = document.createElement('div'); counterElem.id = 'auto-eval-counter'; counterElem.textContent = '已评价: 0 | 未评价: 0'; counterElem.style.cssText = ` margin-top: 5px; padding: 5px; background: #e8f5e9; border-radius: 4px; text-align: center; font-size: 12px; user-select: none; `; panel.appendChild(counterElem); startBtn.onclick = () => { if (!isRunning && navigator.onLine) { isRunning = true; networkErrorCount = 0; // 重置网络错误计数 statusElem.textContent = '状态: 运行中 - 正在扫描未评价课程'; statusElem.style.background = '#e8f5e9'; updateCourseCounter(); tryClickFirstEvaluateBtn(); } else if (!navigator.onLine) { statusElem.textContent = '状态: 网络已断开 - 无法启动'; statusElem.style.background = '#ffebee'; } }; pauseBtn.onclick = () => { pauseEvaluation('用户手动暂停'); }; // 添加全局暂停快捷键 (ESC) document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { pauseEvaluation('用户手动暂停'); } }); btnContainer.appendChild(startBtn); btnContainer.appendChild(pauseBtn); panel.appendChild(btnContainer); panel.appendChild(statusElem); document.body.appendChild(panel); // 存储面板尺寸 panelWidth = panel.offsetWidth; panelHeight = panel.offsetHeight; // 检查面板位置是否在视口内 adjustPanelPosition(); // 添加拖动事件监听器 - 整个面板可拖动 panel.addEventListener('mousedown', startDrag); title.addEventListener('mousedown', startDrag); } // 调整面板位置到视口内 function adjustPanelPosition() { if (!panel) return; const rect = panel.getBoundingClientRect(); let newLeft = rect.left; let newTop = rect.top; // 如果面板右侧超出视口 if (rect.right > window.innerWidth) { newLeft = window.innerWidth - panelWidth; } // 如果面板底部超出视口 if (rect.bottom > window.innerHeight) { newTop = window.innerHeight - panelHeight; } // 如果左侧超出 if (newLeft < 0) { newLeft = 0; } // 如果顶部超出 if (newTop < 0) { newTop = 0; } // 应用调整 if (newLeft !== rect.left || newTop !== rect.top) { panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px'; // 更新存储的位置 panelX = newLeft; panelY = newTop; localStorage.setItem('autoEvalPanelPosition', JSON.stringify({ x: panelX, y: panelY })); } } // 开始拖动面板 function startDrag(e) { // 排除按钮和输入元素 if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } if (e.button !== 0) return; // 只响应左键点击 if (!panel) return; isDragging = true; // 计算鼠标相对于面板左上角的偏移 const rect = panel.getBoundingClientRect(); dragOffsetX = e.clientX - rect.left; dragOffsetY = e.clientY - rect.top; // 添加拖动样式 panel.style.cursor = 'grabbing'; panel.style.boxShadow = '0 8px 24px rgba(0,0,0,0.3)'; panel.style.opacity = '0.9'; document.addEventListener('mousemove', dragPanel); document.addEventListener('mouseup', stopDrag); e.preventDefault(); } // 拖动面板 function dragPanel(e) { if (!isDragging || !panel) return; // 计算新位置 let newLeft = e.clientX - dragOffsetX; let newTop = e.clientY - dragOffsetY; // 限制在窗口范围内 newLeft = Math.max(0, Math.min(window.innerWidth - panelWidth, newLeft)); newTop = Math.max(0, Math.min(window.innerHeight - panelHeight, newTop)); // 应用新位置 panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px'; e.preventDefault(); } // 停止拖动 function stopDrag(e) { if (!isDragging) return; isDragging = false; if (panel) { panel.style.cursor = 'default'; panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)'; panel.style.opacity = '1'; // 保存位置到localStorage const rect = panel.getBoundingClientRect(); panelX = rect.left; panelY = rect.top; localStorage.setItem('autoEvalPanelPosition', JSON.stringify({ x: panelX, y: panelY })); } document.removeEventListener('mousemove', dragPanel); document.removeEventListener('mouseup', stopDrag); e.preventDefault(); } // 暂停评价功能(带原因参数) function pauseEvaluation(reason = '未知原因') { if (isRunning) { isRunning = false; clearTimers(); if (dialogDetectionInterval) { clearInterval(dialogDetectionInterval); dialogDetectionInterval = null; } if (submissionConfirmTimer) { clearTimeout(submissionConfirmTimer); submissionConfirmTimer = null; } if (statusElem) { statusElem.textContent = `状态: 已暂停 (${reason})`; statusElem.style.background = '#ffebee'; } } } // 清除所有定时器 function clearTimers() { if (retryTimer) clearTimeout(retryTimer); if (statusUpdateTimer) clearTimeout(statusUpdateTimer); retryTimer = null; statusUpdateTimer = null; } // 更新课程计数器 function updateCourseCounter() { if (!counterElem) return; const evaluated = document.querySelectorAll('.ypj').length; const notEvaluated = document.querySelectorAll('.wpj').length; counterElem.textContent = `已评价: ${evaluated} | 未评价: ${notEvaluated}`; statusUpdateTimer = setTimeout(updateCourseCounter, 5000); } // 尝试点击第一个未评价课程 function tryClickFirstEvaluateBtn() { if (!isRunning) return; // 检查网络状态 if (!navigator.onLine) { handleNetworkOffline(); return; } // 查找所有未评价课程 const evaluateBtns = document.querySelectorAll('.btn_theme'); const notEvaluatedBtns = Array.from(evaluateBtns).filter(btn => { const row = btn.closest('tr'); const statusCell = row.querySelector('.wpj'); return statusCell && statusCell.textContent.includes('未评价'); }); if (notEvaluatedBtns.length === 0) { // 检查页面中是否还有未评价的课程 const remainingUnEvaluated = document.querySelectorAll('.wpj').length; if (remainingUnEvaluated > 0) { // 更新状态 if (statusElem) { statusElem.textContent = `状态: 检测到 ${remainingUnEvaluated} 个未评价课程 - 重新尝试`; statusElem.style.background = '#fffde7'; } // 稍后重试 retryTimer = setTimeout(tryClickFirstEvaluateBtn, 2000); return; } else { // 确实没有未评价课程了 pauseEvaluation('所有课程已评价完毕'); if (statusElem) { statusElem.textContent = '状态: 所有课程已评价完毕'; statusElem.style.background = '#e8f5e9'; } return; } } // 选择第一个未评价课程 const evaluateBtn = notEvaluatedBtns[0]; const row = evaluateBtn.closest('tr'); // 获取课程ID currentEvaluationId = evaluateBtn.getAttribute('data-id') || row.getAttribute('data-id'); // 更新状态 if (statusElem) { statusElem.textContent = '状态: 正在打开评价对话框...'; } // 尝试点击评价按钮 evaluateBtn.click(); // 启动对话框检测 startDialogDetection(); // 更新课程计数器 updateCourseCounter(); } // 检测对话框是否打开 function startDialogDetection() { if (dialogDetectionInterval) { clearInterval(dialogDetectionInterval); } dialogDetectionInterval = setInterval(() => { if (!isRunning) { clearInterval(dialogDetectionInterval); return; } // 检查网络状态 if (!navigator.onLine) { handleNetworkOffline(); clearInterval(dialogDetectionInterval); return; } const dialog = document.getElementById('pjcz'); if (dialog && window.getComputedStyle(dialog).display !== 'none') { clearInterval(dialogDetectionInterval); dialogDetectionInterval = null; setTimeout(() => { fillEvaluationForm(); }, 800); } }, 500); // 15秒后停止检测 setTimeout(() => { if (dialogDetectionInterval) { clearInterval(dialogDetectionInterval); dialogDetectionInterval = null; if (!document.getElementById('pjcz') || window.getComputedStyle(document.getElementById('pjcz')).display === 'none') { handleDialogOpenFailed(); } } }, 15000); } // 处理对话框打开失败 function handleDialogOpenFailed() { networkErrorCount++; if (networkErrorCount >= MAX_NETWORK_ERRORS) { pauseEvaluation('多次网络错误'); if (statusElem) { statusElem.textContent = '状态: 多次网络错误 - 已暂停'; statusElem.style.background = '#ffebee'; } return; } if (statusElem) { statusElem.textContent = `状态: 对话框打开失败 - 第${networkErrorCount}次重试`; statusElem.style.background = '#fffde7'; } // 继续下一个课程 retryTimer = setTimeout(tryClickFirstEvaluateBtn, 2000); } // 填写评价表单 function fillEvaluationForm() { const dialog = document.getElementById('pjcz'); if (!dialog || window.getComputedStyle(dialog).display === 'none') { handleDialogOpenFailed(); return; } // 检查网络状态 if (!navigator.onLine) { handleNetworkOffline(); return; } const inputs = Array.from(dialog.querySelectorAll('input')) .filter(input => !input.hasAttribute('readonly') && input.offsetParent !== null && (input.type === 'text' || input.type === 'number')); if (inputs.length === 0) { if (statusElem) { statusElem.textContent = '状态: 未找到评分输入框 - 跳过此课程'; statusElem.style.background = '#ffebee'; } const closeBtn = dialog.querySelector('.close'); if (closeBtn) closeBtn.click(); retryTimer = setTimeout(tryClickFirstEvaluateBtn, 2000); return; } const count = inputs.length; const skipIndex = Math.floor(Math.random() * count); inputs.forEach((input, index) => { let score = 10; let node = input.nextSibling; while (node) { if (node.nodeType === Node.TEXT_NODE && /\d+/.test(node.textContent)) { score = parseInt(node.textContent.match(/\d+/)[0], 10); break; } node = node.nextSibling; } input.value = (index === skipIndex) ? (score - 1) : score; triggerEvent(input, 'focus'); triggerEvent(input, 'input'); triggerEvent(input, 'change'); triggerEvent(input, 'blur'); }); const textarea = dialog.querySelector('textarea'); if (textarea) { textarea.value = '老师教学认真负责,课程内容充实,讲解清晰易懂'; triggerEvent(textarea, 'focus'); triggerEvent(textarea, 'input'); triggerEvent(textarea, 'change'); triggerEvent(textarea, 'blur'); } if (statusElem) { statusElem.textContent = '状态: 填分完成 - 正在等待表单验证'; statusElem.style.background = '#fffde7'; } retryTimer = setTimeout(() => { // 检查网络状态 if (!navigator.onLine) { handleNetworkOffline(); return; } const errorElements = dialog.querySelectorAll('.el-form-item__error'); const hasErrors = errorElements.length > 0; if (hasErrors) { if (statusElem) { statusElem.textContent = '状态: 表单验证失败 - 尝试修正'; statusElem.style.background = '#ffebee'; } fillEvaluationForm(); return; } if (statusElem) { statusElem.textContent = '状态: 验证通过 - 3秒后自动提交'; } retryTimer = setTimeout(() => { submitEvaluation(); }, 3000); }, 3000); } // 提交评价 function submitEvaluation() { const dialog = document.getElementById('pjcz'); if (!dialog || window.getComputedStyle(dialog).display === 'none') { handleDialogOpenFailed(); return; } // 检查网络状态 if (!navigator.onLine) { handleNetworkOffline(); return; } const submitButtons = Array.from(dialog.querySelectorAll('button, span')); const submitButton = submitButtons.find(el => el.textContent.trim() === '提交' || el.textContent.trim() === '确定' || el.textContent.trim() === '提交评价' ); if (submitButton) { if (statusElem) { statusElem.textContent = '状态: 提交中...'; statusElem.style.background = '#e3f2fd'; } submitButton.scrollIntoView({behavior: 'smooth', block: 'center'}); // 增加随机延迟(1-3秒)避免过快提交 const delay = 1000 + Math.random() * 2000; setTimeout(() => { const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); submitButton.dispatchEvent(clickEvent); // 启动提交确认检查 startSubmissionConfirmation(); }, delay); } else { if (statusElem) { statusElem.textContent = '状态: 提交失败 - 跳过此课程'; statusElem.style.background = '#ffebee'; } retryTimer = setTimeout(tryClickFirstEvaluateBtn, 2000); } } // 启动提交确认检查 function startSubmissionConfirmation() { // 清除之前的定时器 if (submissionConfirmTimer) { clearTimeout(submissionConfirmTimer); } // 设置确认检查 submissionConfirmTimer = setTimeout(() => { checkSubmissionStatus(); }, 5000); // 5秒后检查提交状态 } // 检查提交状态 function checkSubmissionStatus() { const dialog = document.getElementById('pjcz'); // 情况1:对话框已关闭 - 提交成功 if (!dialog || window.getComputedStyle(dialog).display === 'none') { if (statusElem) { statusElem.textContent = '状态: 提交成功 - 等待页面刷新'; statusElem.style.background = '#e8f5e9'; } // 等待页面自动刷新(通常2-3秒) setTimeout(() => { if (statusElem) { statusElem.textContent = '状态: 页面刷新完成 - 继续下一个'; } // 继续下一个课程 retryTimer = setTimeout(tryClickFirstEvaluateBtn, 1000); }, 3000); return; } // 情况2:对话框仍然打开 - 提交可能失败 let hasError = false; // 检查错误提示 const errorMessages = dialog.querySelectorAll('.el-form-item__error, .el-message__content, .el-message-box__content, .error-msg'); if (errorMessages.length > 0) { hasError = true; } else { // 检查文本内容 const dialogText = dialog.textContent; if (dialogText.includes('错误') || dialogText.includes('失败') || dialogText.includes('请重新') || dialogText.includes('提交失败')) { hasError = true; } } if (statusElem) { if (hasError) { statusElem.textContent = '状态: 提交失败 - 发现错误提示'; statusElem.style.background = '#ffebee'; } else { statusElem.textContent = '状态: 提交未完成(可能网络延迟)- 跳过此课程'; statusElem.style.background = '#ffebee'; } } // 尝试关闭对话框 const closeBtn = dialog.querySelector('.close'); if (closeBtn) { closeBtn.click(); } // 继续下一个课程 retryTimer = setTimeout(tryClickFirstEvaluateBtn, 2000); } // 触发事件 function triggerEvent(element, eventName) { if (!element) return; const event = new Event(eventName, { bubbles: true, cancelable: true }); element.dispatchEvent(event); } // 清理函数:在脚本卸载时清除定时器和事件 function cleanup() { clearTimers(); if (dialogDetectionInterval) { clearInterval(dialogDetectionInterval); dialogDetectionInterval = null; } if (submissionConfirmTimer) { clearTimeout(submissionConfirmTimer); submissionConfirmTimer = null; } // 移除事件监听器 window.removeEventListener('online', handleNetworkOnline); window.removeEventListener('offline', handleNetworkOffline); window.removeEventListener('popstate', handleRouteChange); document.removeEventListener('keydown', pauseEvaluation); document.removeEventListener('mousemove', dragPanel); document.removeEventListener('mouseup', stopDrag); } // 页面卸载时清理 window.addEventListener('beforeunload', cleanup); // 页面加载时立即初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', mainInit); } else { mainInit(); } })();