您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在华侨大学教务评教列表页添加“一键完成”和“设置”按钮,自动依次填写、确认并提交所有评教问卷,支持自定义答题间隔。
// ==UserScript== // @name 一键评教脚本 (HQU) - 全自动版 // @namespace http://tampermonkey.net/ // @version 2.3 // @description 在华侨大学教务评教列表页添加“一键完成”和“设置”按钮,自动依次填写、确认并提交所有评教问卷,支持自定义答题间隔。 // @author Gemini // @match https://jwapp.hqu.edu.cn/jwapp/sys/pjapp/*default/index.do* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 配置 --- const OPINION_TEXT = '课程很好,无其他意见'; // 自动填写的评语 let ANSWER_DELAY = GM_getValue('answer_delay', 500); // 从存储读取答题间隔,默认为500ms /** * 延时函数 * @param {number} ms - 毫秒数 */ function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 等待指定元素出现 * @param {string} selector - CSS选择器 * @param {number} timeout - 超时时间 * @returns {Promise<Element>} */ function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const intervalTime = 100; let elapsedTime = 0; const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); resolve(element); } elapsedTime += intervalTime; if (elapsedTime >= timeout) { clearInterval(interval); reject(new Error(`等待元素超时: ${selector}`)); } }, intervalTime); }); } /** * 等待指定元素消失 * @param {string} selector - CSS选择器 * @param {number} timeout - 超时时间 */ function waitForElementToDisappear(selector, timeout = 10000) { return new Promise((resolve, reject) => { const intervalTime = 100; let elapsedTime = 0; const interval = setInterval(() => { if (!document.querySelector(selector)) { clearInterval(interval); resolve(); } elapsedTime += intervalTime; if (elapsedTime >= timeout) { clearInterval(interval); reject(new Error(`等待元素消失超时: ${selector}`)); } }, intervalTime); }); } /** * 填写并提交单个问卷 (已修复二次确认) */ async function fillAndSubmitForm() { console.log("检测到问卷,开始填写..."); await waitForElement('.wjtxQuestionItem'); await delay(500); const questionItems = document.querySelectorAll('.wjtxQuestionItem'); for (const item of questionItems) { // 1. 单选题 const firstRadio = item.querySelector('.wjtxQuestionOptionItem .wjtxRadioButton'); if (firstRadio) firstRadio.click(); // 2. 多选题 const checkBoxes = item.querySelectorAll('.jqx-checkbox'); if (checkBoxes.length > 0) { checkBoxes.forEach(box => { const labelText = box.textContent || box.innerText; if (labelText && !labelText.includes('基本无变化')) { if (box.getAttribute('aria-checked') === 'false') { const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); const mouseUpEvent = new MouseEvent('mouseup', { bubbles: true, cancelable: true }); box.dispatchEvent(mouseDownEvent); box.dispatchEvent(mouseUpEvent); } } }); } // 3. 主观题 const textArea = item.querySelector('textarea.jqx-text-area-element'); if (textArea) { textArea.focus(); textArea.value = OPINION_TEXT; textArea.dispatchEvent(new Event('input', { bubbles: true })); textArea.dispatchEvent(new Event('change', { bubbles: true })); textArea.blur(); } // 模拟人工答题间隔 console.log(`- 答题间隔 ${ANSWER_DELAY}ms`); await delay(ANSWER_DELAY); } console.log("问卷填写完毕,准备提交..."); await delay(500); const initialSubmitButton = Array.from(document.querySelectorAll('.bh-paper-pile-dialog .wjtxOperationButtonContainer button')) .find(btn => btn.textContent.trim() === '提交'); if (!initialSubmitButton) throw new Error("未找到“提交”按钮!"); initialSubmitButton.click(); console.log("已点击“提交”按钮,等待确认弹窗..."); // FIX: 处理新的二次确认弹窗 try { const finalConfirmButtonSelector = '.bh-dialog-btn.bh-bg-primary'; const finalConfirmButton = await waitForElement(finalConfirmButtonSelector, 3000); console.log("检测到确认对话框,正在点击最终确认..."); finalConfirmButton.click(); } catch (e) { console.warn("未检测到二次确认弹窗,脚本将继续执行。"); } } /** * 主流程:处理所有评教 */ async function processAllEvaluations(button) { const originalButtonText = button.innerHTML; button.disabled = true; document.getElementById('eval-settings-btn').disabled = true; const evalButtons = Array.from(document.querySelectorAll('.wj-wjxx .bh-btn-primary')) .filter(btn => btn.textContent.trim() === '立刻评教'); if (evalButtons.length === 0) { alert("在当前页面未找到任何“立刻评教”的按钮。\n请确认您在待评课程列表页。"); button.disabled = false; document.getElementById('eval-settings-btn').disabled = false; return; } const total = evalButtons.length; console.log(`发现 ${total} 个待评教课程。`); for (let i = 0; i < total; i++) { const currentEvalButton = Array.from(document.querySelectorAll('.wj-wjxx .bh-btn-primary')).find(btn => btn.textContent.trim() === '立刻评教'); if(!currentEvalButton) { console.log("找不到下一个“立刻评教”按钮,可能已经全部完成。"); break; } const courseTitle = currentEvalButton.closest('.wjx-container').querySelector('.wjxSubTitle').textContent.trim(); console.log(`--- 开始处理第 ${i + 1} / ${total} 个: ${courseTitle} ---`); button.innerHTML = `⚡ 处理中 (${i + 1}/${total})...`; try { currentEvalButton.click(); await fillAndSubmitForm(); await waitForElementToDisappear('.bh-paper-pile-dialog'); console.log(`--- ${courseTitle} 处理完成 ---`); await delay(1500); } catch (error) { console.error(`处理课程 "${courseTitle}" 时出错:`, error); alert(`处理课程 "${courseTitle}" 时发生错误,已停止。\n请检查控制台获取详细信息,然后刷新页面重试。`); button.innerHTML = originalButtonText; button.disabled = false; document.getElementById('eval-settings-btn').disabled = false; return; } } button.innerHTML = '🎉 全部完成'; button.style.background = 'linear-gradient(45deg, #28a745, #218838)'; document.getElementById('eval-settings-btn').disabled = true; alert(`所有 ${total} 个评教已自动完成并提交!`); console.log("所有评教已处理完毕!"); } /** * 创建UI界面 (已更新,增加设置按钮) */ function setupUI() { if (document.getElementById('eval-button-container')) return; GM_addStyle(` #eval-button-container { position: absolute; top: 8px; right: 0px; z-index: 999; display: flex; gap: 10px; } .eval-btn { padding: 6px 16px; font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-weight: bold; color: white; border: none; border-radius: 18px; cursor: pointer; transition: all 0.3s ease; } .eval-btn:hover:not(:disabled) { transform: translateY(-2px); } #process-all-eval-btn { background: linear-gradient(45deg, #1E90FF, #0073e6); box-shadow: 0 2px 8px rgba(0, 123, 255, 0.4); } #process-all-eval-btn:hover:not(:disabled) { box-shadow: 0 4px 12px rgba(0, 123, 255, 0.5); } #eval-settings-btn { padding: 6px 12px; background: linear-gradient(45deg, #6c757d, #5a6268); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } #eval-settings-btn:hover:not(:disabled) { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } .eval-btn:disabled { background: linear-gradient(45deg, #9e9e9e, #888888); cursor: not-allowed; box-shadow: none; transform: none; } `); const targetContainer = document.querySelector('#wdpj-wj-section .wdpj-bh-buttons'); if (targetContainer) { targetContainer.style.position = 'relative'; const buttonContainer = document.createElement('div'); buttonContainer.id = 'eval-button-container'; const processAllButton = document.createElement('button'); processAllButton.id = 'process-all-eval-btn'; processAllButton.className = 'eval-btn'; processAllButton.innerHTML = '⚡ 一键完成所有评教'; processAllButton.addEventListener('click', () => processAllEvaluations(processAllButton)); const settingsButton = document.createElement('button'); settingsButton.id = 'eval-settings-btn'; settingsButton.className = 'eval-btn'; settingsButton.innerHTML = '⚙️ 设置'; settingsButton.addEventListener('click', () => { const newDelay = prompt(`请输入每个问题之间的答题间隔(单位:毫秒):\n推荐值为 200 到 1000 之间。`, ANSWER_DELAY); if (newDelay !== null && !isNaN(newDelay) && newDelay >= 0) { const parsedDelay = parseInt(newDelay, 10); GM_setValue('answer_delay', parsedDelay); ANSWER_DELAY = parsedDelay; alert(`设置成功!新的答题间隔为 ${parsedDelay} 毫秒。`); console.log(`答题间隔已更新为: ${parsedDelay}ms`); } else if (newDelay !== null) { alert('输入无效,请输入一个非负数字。'); } }); buttonContainer.appendChild(settingsButton); buttonContainer.appendChild(processAllButton); targetContainer.appendChild(buttonContainer); } } window.addEventListener('hashchange', () => setTimeout(setupUI, 1000)); window.addEventListener('load', () => setTimeout(setupUI, 1000)); })();