// ==UserScript== // @name GUET评教+教材评价助手 // @namespace http://tampermonkey.net/ // @version 2.4.0 // @description 桂林电子科技大学教务系统一键评教脚本,支持自动登录、自动填写、自动循环评教、教材评价 // @author Deng Kai // @match https://bkjwtest.guet.edu.cn/* // @match https://bkjw.guet.edu.cn/* // @match https://bkjwtest.guet.edu.cn/evaluation-student-frontend/* // @match https://bkjw.guet.edu.cn/evaluation-student-frontend/* // @match https://bkjwtest.guet.edu.cn/student/for-std/textbook-evaluate/* // @match https://bkjw.guet.edu.cn/student/for-std/textbook-evaluate/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // ==================== 配置区域 ==================== // 默认评语模板 const DEFAULT_COMMENTS = { // 问题11:对课程的印象(50-500字) courseComment: "这门课程内容丰富,老师讲解清晰易懂,理论与实践结合紧密。通过学习,我对相关知识有了更深入的理解,提升了分析和解决问题的能力。老师认真负责,课堂氛围活跃,能够激发学生的学习兴趣。希望今后能有更多实践机会。", // 问题12:对老师的印象(5-100字) teacherComment: "老师教学认真负责,关心学生,讲课生动有趣,是一位优秀的教师。" }; // 页面URL配置 const URLS = { login: '/student/ldap/login', home: '/student/home', evaluation: '/evaluation-student-frontend/', evaluationDirect: '/student/for-std/extra-system/student-summation-forstudent/index', textbookEvaluate: '/student/for-std/textbook-evaluate' }; // 自动循环评教状态 let autoLoopEnabled = false; let autoLoopInProgress = false; // ==================== 页面类型检测 ==================== function getPageType() { const url = window.location.href; if (url.includes(URLS.login)) return 'login'; // 教材评价页面 if (url.includes(URLS.textbookEvaluate)) return 'textbook-evaluate'; // 评教表单页面(填写评分的页面) if (url.includes('student-summation-evaluation')) return 'evaluation'; // 评教列表页面(选择老师的页面) if (url.includes(URLS.evaluationDirect) || url.includes('student-summation-forstudent')) return 'evaluation'; if (url.includes(URLS.evaluation)) return 'evaluation'; if (url.includes(URLS.home)) return 'home'; return 'other'; } // ==================== 登录凭据存储 ==================== function saveCredentials(username, password) { GM_setValue('guet_username', username); GM_setValue('guet_password', password); } function loadCredentials() { return { username: GM_getValue('guet_username', ''), password: GM_getValue('guet_password', '') }; } function clearCredentials() { GM_setValue('guet_username', ''); GM_setValue('guet_password', ''); } // ==================== 样式 ==================== GM_addStyle(` .guet-eval-panel { position: fixed; top: 10px; right: 10px; z-index: 99999; background: linear-gradient(145deg, rgba(15, 18, 25, 0.96), rgba(10, 13, 20, 0.96)); backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(64, 158, 255, 0.15); border-radius: 16px; padding: 20px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(64, 158, 255, 0.1), inset 0 1px 0 rgba(64, 158, 255, 0.08); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; min-width: 300px; max-width: 340px; cursor: move; user-select: none; } .guet-eval-panel h3 { background: linear-gradient(135deg, #4A9EFF, #00D4FF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin: 0 0 16px 0; font-size: 18px; font-weight: 700; text-align: center; cursor: move; letter-spacing: 1px; } .guet-eval-panel input, .guet-eval-panel textarea, .guet-eval-panel button { cursor: auto; } .guet-eval-btn { display: block; width: 100%; padding: 11px 16px; margin: 10px 0; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 10px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); background: rgba(45, 48, 58, 0.6); color: rgba(255, 255, 255, 0.9); position: relative; overflow: hidden; } .guet-eval-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); transition: left 0.5s; } .guet-eval-btn:hover::before { left: 100%; } .guet-eval-btn:hover { background: rgba(55, 58, 68, 0.8); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); border-color: rgba(255, 255, 255, 0.12); } .guet-eval-btn:active { transform: translateY(0); } .guet-eval-btn-primary { background: linear-gradient(135deg, rgba(64, 158, 255, 0.85), rgba(48, 138, 235, 0.85)); color: white; border: 1px solid rgba(64, 158, 255, 0.3); box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2); } .guet-eval-btn-primary:hover { background: linear-gradient(135deg, rgba(64, 158, 255, 1), rgba(48, 138, 235, 1)); box-shadow: 0 6px 20px rgba(64, 158, 255, 0.35); } .guet-eval-btn-secondary { background: rgba(45, 48, 58, 0.6); color: rgba(255, 255, 255, 0.85); border: 1px solid rgba(255, 255, 255, 0.1); } .guet-eval-btn-secondary:hover { background: rgba(55, 58, 68, 0.8); border-color: rgba(255, 255, 255, 0.15); } .guet-eval-btn-warning { background: rgba(45, 48, 58, 0.6); color: rgba(255, 255, 255, 0.85); border: 1px solid rgba(255, 255, 255, 0.1); } .guet-eval-btn-warning:hover { background: rgba(55, 58, 68, 0.8); border-color: rgba(255, 255, 255, 0.15); } .guet-eval-btn-save { background: rgba(45, 48, 58, 0.6); color: rgba(255, 255, 255, 0.85); padding: 7px 14px; font-size: 12px; margin: 6px 0; border: 1px solid rgba(255, 255, 255, 0.08); } .guet-eval-btn-save:hover { background: rgba(55, 58, 68, 0.8); border-color: rgba(255, 255, 255, 0.12); } .guet-eval-input { width: 100%; padding: 10px 12px; margin: 6px 0; border: 1px solid rgba(64, 158, 255, 0.2); border-radius: 8px; box-sizing: border-box; background: rgba(20, 25, 35, 0.8); color: #E8F4FF; font-size: 13px; transition: all 0.3s ease; } .guet-eval-input:focus { outline: none; border-color: rgba(64, 158, 255, 0.6); background: rgba(25, 30, 40, 0.9); box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.15); } .guet-eval-input::placeholder { color: rgba(100, 150, 200, 0.4); } .guet-eval-label { color: #A0C8FF; font-size: 13px; margin-top: 10px; display: block; font-weight: 500; letter-spacing: 0.3px; } .guet-eval-label-hint { color: rgba(100, 150, 200, 0.5); font-size: 11px; margin-top: 3px; display: block; } .guet-eval-status { color: #4A9EFF; font-size: 13px; text-align: center; margin-top: 12px; padding: 10px; background: rgba(64, 158, 255, 0.1); border-radius: 8px; border: 1px solid rgba(64, 158, 255, 0.25); font-weight: 500; } .guet-eval-minimize { position: absolute; top: 8px; right: 12px; background: none; border: none; color: rgba(64, 158, 255, 0.6); font-size: 20px; cursor: pointer; transition: all 0.2s ease; width: 28px; height: 28px; border-radius: 6px; display: flex; align-items: center; justify-content: center; } .guet-eval-minimize:hover { background: rgba(64, 158, 255, 0.15); color: #4A9EFF; } .guet-eval-info { color: #B8D8FF; font-size: 12px; text-align: center; margin: 10px 0; padding: 12px; background: rgba(64, 158, 255, 0.08); border-radius: 8px; border: 1px solid rgba(64, 158, 255, 0.2); line-height: 1.6; } .guet-eval-divider { border: none; height: 1px; background: linear-gradient(90deg, transparent, rgba(64, 158, 255, 0.3), transparent); margin: 16px 0; } .guet-eval-btn-login { background: rgba(45, 48, 58, 0.6); color: rgba(255, 255, 255, 0.85); border: 1px solid rgba(255, 255, 255, 0.1); } .guet-eval-btn-login:hover { background: rgba(55, 58, 68, 0.8); border-color: rgba(255, 255, 255, 0.15); } .guet-eval-btn-danger { background: rgba(45, 48, 58, 0.6); color: rgba(255, 255, 255, 0.85); padding: 7px 14px; font-size: 12px; border: 1px solid rgba(255, 255, 255, 0.08); } .guet-eval-btn-danger:hover { background: rgba(55, 58, 68, 0.8); border-color: rgba(255, 255, 255, 0.12); } .guet-eval-btn-auto { background: linear-gradient(135deg, rgba(64, 158, 255, 0.85), rgba(48, 138, 235, 0.85)); color: white; border: 1px solid rgba(64, 158, 255, 0.3); box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2); } .guet-eval-btn-auto:hover { background: linear-gradient(135deg, rgba(64, 158, 255, 1), rgba(48, 138, 235, 1)); box-shadow: 0 6px 20px rgba(64, 158, 255, 0.35); } .guet-eval-progress { background: rgba(255, 255, 255, 0.06); border-radius: 12px; height: 6px; margin: 12px 0; overflow: hidden; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); } .guet-eval-progress-bar { background: linear-gradient(90deg, rgba(64, 158, 255, 0.9), rgba(48, 138, 235, 0.9)); height: 100%; transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 0 8px rgba(64, 158, 255, 0.5); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: bold; } .guet-eval-author { background: linear-gradient(135deg, #4A9EFF, #00D4FF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-size: 14px; font-weight: 600; text-align: center; margin-bottom: 12px; padding: 12px; border-radius: 8px; border: 1px solid rgba(64, 158, 255, 0.25); letter-spacing: 0.8px; } .guet-eval-qq { color: rgba(160, 200, 255, 0.8); font-size: 11px; text-align: center; margin: -8px 0 10px 0; } .guet-eval-author-note { color: rgba(160, 200, 255, 0.6); font-size: 11px; font-weight: 400; margin-top: 5px; letter-spacing: 0.3px; } `); // ==================== 工具函数 ==================== // 等待元素出现 function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if (element) { resolve(element); return; } const observer = new MutationObserver((mutations, obs) => { const el = document.querySelector(selector); if (el) { obs.disconnect(); resolve(el); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); reject(new Error(`Element ${selector} not found within ${timeout}ms`)); }, timeout); }); } // 模拟输入(触发Vue响应) function simulateInput(element, value) { element.value = value; element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); element.dispatchEvent(new Event('blur', { bubbles: true })); } // 模拟点击 function simulateClick(element) { element.click(); element.dispatchEvent(new MouseEvent('click', { bubbles: true })); } // 延迟函数 function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // ==================== 自动循环评教功能 ==================== // 检测是否在老师列表页面(评教列表页) function isTeacherListPage() { // 检查是否有老师名字链接(el-link--inner) const teacherLinks = document.querySelectorAll('span.el-link--inner'); if (teacherLinks.length > 0) { return true; } // 检查是否有"只看未评"复选框(评教列表页特征) const checkbox = document.querySelector('.el-checkbox__label'); if (checkbox && checkbox.textContent.includes('只看未评')) { return true; } // 检查是否有 el-link 组件 const elLinks = document.querySelectorAll('.el-link'); if (elLinks.length > 0) { return true; } return false; } // 检测是否在评教表单页面 function isEvalFormPage() { // 检查是否有评分输入框 const scoreInputs = document.querySelectorAll('input[placeholder="请输入或选择"]'); // 检查是否有评语文本框 const textareas = document.querySelectorAll('textarea'); // 检查是否有提交按钮 const submitBtn = document.querySelector('button'); return scoreInputs.length > 0 || (textareas.length > 0 && submitBtn); } // 查找并点击下一个未评教的老师 async function clickNextTeacher() { console.log('[评教助手] 正在查找下一个未评教的老师...'); // 等待页面加载 await delay(1500); // 查找 el-link--inner 中的老师名字,然后点击其父级 元素 // 结构:老师名字 const teacherSpans = document.querySelectorAll('span.el-link--inner'); for (const span of teacherSpans) { const text = span.textContent?.trim(); // 老师名字通常是2-4个汉字,排除"详情"等链接 if (text && text.length >= 2 && text.length <= 4 && /^[\u4e00-\u9fa5]+$/.test(text) && text !== '详情') { console.log('[评教助手] 找到未评教老师:' + text); // 点击父级 元素 const parentLink = span.closest('a.el-link'); if (parentLink) { console.log('[评教助手] 点击老师链接...'); parentLink.click(); return true; } } } console.log('[评教助手] 未找到更多未评教的老师,可能已全部完成!'); return false; } // 获取评教统计信息 function getEvalStats() { let total = 0; let completed = 0; let pending = 0; const rows = document.querySelectorAll('table tbody tr, .el-table__body tr'); for (const row of rows) { const rowText = row.textContent || ''; if (rowText.includes('评教') || rowText.includes('评价')) { total++; if (rowText.includes('已评') || rowText.includes('完成')) { completed++; } else if (rowText.includes('未评') || rowText.includes('待评') || rowText.includes('去评教')) { pending++; } } } return { total, completed, pending }; } // 自动循环评教主流程 async function startAutoLoop(statusEl) { if (autoLoopInProgress) { statusEl.textContent = '⚠️ 自动评教已在进行中'; return; } autoLoopEnabled = true; autoLoopInProgress = true; GM_setValue('autoLoopEnabled', true); statusEl.textContent = '🔄 自动循环评教已启动...'; console.log('[评教助手] 自动循环评教已启动'); // 等待页面稳定 await delay(1000); // 检查当前页面状态(优先检查列表页面,避免误判) if (isTeacherListPage()) { // 在老师列表页面,点击第一个老师 statusEl.textContent = '🔍 检测到老师列表,正在点击第一个老师...'; console.log('[评教助手] 当前在老师列表页面,准备点击第一个老师'); await delay(500); const found = await clickNextTeacher(); if (!found) { stopAutoLoop(statusEl, '🎉 所有老师评教完成!'); } else { statusEl.textContent = '⏳ 正在进入评教页面...'; } } else if (isEvalFormPage()) { // 在评教表单页面,执行填写和提交 statusEl.textContent = '📝 检测到评教表单,开始自动填写...'; await autoFillAndSubmit(statusEl); } else { statusEl.textContent = '⚠️ 请先进入评教页面'; autoLoopInProgress = false; autoLoopEnabled = false; GM_setValue('autoLoopEnabled', false); } } // 停止自动循环 function stopAutoLoop(statusEl, message = '⏹️ 自动评教已停止') { autoLoopEnabled = false; autoLoopInProgress = false; GM_setValue('autoLoopEnabled', false); if (statusEl) { statusEl.textContent = message; } console.log('[评教助手] ' + message); } // 自动填写并提交(用于自动循环模式) async function autoFillAndSubmit(statusEl) { try { const savedComments = loadSavedComments(); const minScore = GM_getValue('minScore', 8); // 填写所有内容 await fillAll(savedComments.courseComment, savedComments.teacherComment, minScore, statusEl); await delay(1000); // 自动提交 statusEl.textContent = '🚀 正在提交...'; const submitted = await autoSubmit(statusEl); if (submitted) { // 提交成功后等待一下,然后关闭当前标签页(回到列表页面) statusEl.textContent = '✅ 提交成功!即将返回列表...'; await delay(2000); // 关闭当前标签页,让用户回到列表页面继续 window.close(); } } catch (e) { console.error('[评教助手] 自动填写提交出错:', e); statusEl.textContent = '❌ 出错: ' + e.message; stopAutoLoop(statusEl); } } // ==================== 核心功能 ==================== // 填写打分题:自动识别所有评分输入框,每题随机选择分数 // minScore 是用户设置的下限比例基准(针对满分10分),会按比例换算到其他满分 async function fillScores(minScore = 8) { const scoreInputs = document.querySelectorAll('input[placeholder="请输入或选择"]'); let filledCount = 0; const scores = []; // 记录每题的分数 for (const input of scoreInputs) { try { // 点击输入框打开下拉选择器 simulateClick(input); await delay(500); // 检测下拉菜单中的最大分数(满分) const scoreOptions = document.querySelectorAll('.dropdown-menu-item-wrapper .inner'); let maxScore = 10; // 默认满分10 for (const option of scoreOptions) { const optionValue = parseInt(option.textContent.trim()); if (!isNaN(optionValue) && optionValue > maxScore) { maxScore = optionValue; } } // 根据满分动态计算分数下限 let actualMinScore; if (maxScore === 10) { actualMinScore = minScore; } else if (maxScore === 15) { actualMinScore = Math.max(11, Math.ceil(minScore / 10 * 15)); } else { actualMinScore = Math.ceil(minScore / 10 * maxScore); } // 随机选择 actualMinScore 到 maxScore 之间的分数 const score = Math.floor(Math.random() * (maxScore - actualMinScore + 1)) + actualMinScore; scores.push(`${score}/${maxScore}`); // 查找下拉菜单中的数字选项并点击 let clicked = false; for (const option of scoreOptions) { if (option.textContent.trim() === score.toString()) { simulateClick(option); clicked = true; await delay(300); break; } } // 备用方案:查找 listbox 中的选项 if (!clicked) { const listItems = document.querySelectorAll('ul[role="listbox"] li div'); for (const item of listItems) { if (item.textContent.trim() === score.toString()) { simulateClick(item); clicked = true; await delay(300); break; } } } // 点击"确认"按钮 if (clicked) { await delay(200); const confirmBtn = document.querySelector('.foot-btn.foot-btn-primary'); if (confirmBtn) { simulateClick(confirmBtn); await delay(400); } else { const allBtns = document.querySelectorAll('.dropdown-menu-foot div, .foot-btn'); for (const btn of allBtns) { if (btn.textContent.trim() === '确认') { simulateClick(btn); await delay(400); break; } } } } await delay(300 + Math.random() * 400); filledCount++; } catch (e) { console.error('填写分数失败:', e); } } console.log(`[评教助手] 评分填写完成,共${filledCount}题,各题分数:${scores.join(', ')}`); return { filledCount, minScore, scores }; } // 填写文字评价(问题11和12) async function fillComments(courseComment, teacherComment) { const textareas = document.querySelectorAll('textarea'); let filledCount = 0; for (const textarea of textareas) { const placeholder = textarea.placeholder || ''; if (placeholder.includes('50~500') || placeholder.includes('50-500')) { // 问题11:课程评价 simulateInput(textarea, courseComment); filledCount++; } else if (placeholder.includes('5~100') || placeholder.includes('5-100')) { // 问题12:老师评价 simulateInput(textarea, teacherComment); filledCount++; } await delay(500 + Math.random() * 300); // 随机延迟500-800ms } return filledCount; } // 填写单选题(问题13和14) async function fillRadios() { const radioGroups = document.querySelectorAll('[role="radiogroup"]'); let filledCount = 0; let groupIndex = 0; for (const group of radioGroups) { const radios = group.querySelectorAll('[role="radio"]'); for (const radio of radios) { const text = radio.textContent || ''; // 问题13:是否推荐 - 选择"是(0分)" if (groupIndex === 0 && text.includes('是') && text.includes('0分')) { simulateClick(radio); filledCount++; console.log('[评教助手] 问题13选择: 是(0分)'); break; } // 问题14:师德师风 - 选择"优秀(0分)" if (groupIndex === 1 && text.includes('优秀') && text.includes('0分')) { simulateClick(radio); filledCount++; console.log('[评教助手] 问题14选择: 优秀(0分)'); break; } } groupIndex++; await delay(500 + Math.random() * 300); // 随机延迟500-800ms } return filledCount; } // 一键填写所有内容 async function fillAll(courseComment, teacherComment, minScore, statusEl) { try { statusEl.textContent = '正在填写评分(1-10题)...'; const scoreResult = await fillScores(minScore); statusEl.textContent = `已填写 ${scoreResult.filledCount} 个评分`; await delay(300); statusEl.textContent = '正在填写评语(11-12题)...'; const commentCount = await fillComments(courseComment, teacherComment); statusEl.textContent = `已填写 ${commentCount} 个评语`; await delay(300); statusEl.textContent = '正在填写单选题(13-14题)...'; const radioCount = await fillRadios(); statusEl.textContent = `已填写 ${radioCount} 个单选题`; await delay(300); statusEl.textContent = `✅ 完成!共填写 ${scoreResult.filledCount + commentCount + radioCount} 项`; } catch (e) { statusEl.textContent = '❌ 填写出错: ' + e.message; console.error('填写出错:', e); } } // 自动提交 async function autoSubmit(statusEl) { try { const buttons = document.querySelectorAll('button'); for (const btn of buttons) { if (btn.textContent.includes('匿名提交') || btn.textContent.includes('提交')) { statusEl.textContent = '正在提交...'; simulateClick(btn); statusEl.textContent = '✅ 已点击提交按钮'; return true; } } statusEl.textContent = '❌ 未找到提交按钮'; return false; } catch (e) { statusEl.textContent = '❌ 提交出错: ' + e.message; return false; } } // ==================== 登录功能 ==================== // 执行登录 async function doLogin(username, password, statusEl) { try { statusEl.textContent = '正在登录...'; // 查找用户名输入框 const usernameInput = document.querySelector('input[placeholder*="账号"], input[placeholder*="用户名"], input[name="username"], input[type="text"]'); if (!usernameInput) { statusEl.textContent = '❌ 未找到用户名输入框'; return false; } // 查找密码输入框 const passwordInput = document.querySelector('input[placeholder*="密码"], input[name="password"], input[type="password"]'); if (!passwordInput) { statusEl.textContent = '❌ 未找到密码输入框'; return false; } // 填写用户名和密码 simulateInput(usernameInput, username); await delay(200); simulateInput(passwordInput, password); await delay(200); // 查找登录按钮 const loginBtn = document.querySelector('button[type="submit"], button.el-button--primary, input[type="submit"]'); if (!loginBtn) { // 备用:查找包含"登录"文字的按钮 const buttons = document.querySelectorAll('button'); for (const btn of buttons) { if (btn.textContent.includes('登录') || btn.textContent.includes('Login')) { simulateClick(btn); statusEl.textContent = '✅ 已点击登录按钮,等待跳转...'; return true; } } statusEl.textContent = '❌ 未找到登录按钮'; return false; } simulateClick(loginBtn); statusEl.textContent = '✅ 已点击登录按钮,等待跳转...'; return true; } catch (e) { statusEl.textContent = '❌ 登录出错: ' + e.message; return false; } } // ==================== 首页导航功能 ==================== // 进入评教页面 async function goToEvaluation(statusEl) { try { statusEl.textContent = '正在跳转到评教页面...'; // 直接跳转到评教页面(避免 iframe 嵌套问题) window.location.href = 'https://bkjwtest.guet.edu.cn/student/for-std/extra-system/student-summation-forstudent/index'; return true; } catch (e) { statusEl.textContent = '❌ 导航出错: ' + e.message; return false; } } // ==================== 本地存储 ==================== // 加载保存的评语 function loadSavedComments() { return { courseComment: GM_getValue('courseComment', DEFAULT_COMMENTS.courseComment), teacherComment: GM_getValue('teacherComment', DEFAULT_COMMENTS.teacherComment) }; } // 保存评语到本地 function saveComments(courseComment, teacherComment) { GM_setValue('courseComment', courseComment); GM_setValue('teacherComment', teacherComment); } // ==================== UI面板 ==================== // 创建登录页面面板 function createLoginPanel() { if (document.querySelector('.guet-eval-panel')) { document.querySelector('.guet-eval-panel').remove(); } const credentials = loadCredentials(); const panel = document.createElement('div'); panel.className = 'guet-eval-panel'; panel.innerHTML = `

🎓GUET评教+教材评价助手

By Deng Kai
仅供学习交流,请勿用于商业用途
使用方法进QQ群自取:1081070075
🔐 请输入教务系统账号密码
登录后将自动进入评教页面
${credentials.username ? '' : ''}
等待登录...
`; document.body.appendChild(panel); const statusEl = document.getElementById('guet-status'); // 登录按钮事件 document.getElementById('guet-login').addEventListener('click', async () => { const username = document.getElementById('guet-username').value.trim(); const password = document.getElementById('guet-password').value; const remember = document.getElementById('guet-remember').checked; if (!username || !password) { statusEl.textContent = '❌ 请输入账号和密码'; return; } if (remember) { saveCredentials(username, password); statusEl.textContent = '✅ 账号已保存'; } await doLogin(username, password, statusEl); }); // 清除账号按钮 const clearBtn = document.getElementById('guet-clear-credentials'); if (clearBtn) { clearBtn.addEventListener('click', () => { clearCredentials(); document.getElementById('guet-username').value = ''; document.getElementById('guet-password').value = ''; document.getElementById('guet-remember').checked = false; statusEl.textContent = '✅ 已清除保存的账号'; clearBtn.remove(); }); } // 最小化功能 setupMinimize(panel); // 拖动功能 setupDrag(panel); // 如果已保存账号,自动填充并提示 if (credentials.username && credentials.password) { statusEl.textContent = '已加载保存的账号,点击登录即可'; } } // 创建首页面板(登录后的首页) function createHomePanel() { if (document.querySelector('.guet-eval-panel')) { document.querySelector('.guet-eval-panel').remove(); } const panel = document.createElement('div'); panel.className = 'guet-eval-panel guet-eval-panel-home'; panel.innerHTML = `

🎓 GUET评教+教材评价助手

By Deng Kai
仅供学习交流,请勿用于商业用途
使用方法进QQ群自取:1081070075
✅ 登录成功!
请选择要进入的功能
请选择功能
`; document.body.appendChild(panel); const statusEl = document.getElementById('guet-status'); document.getElementById('guet-go-evaluation').addEventListener('click', () => { statusEl.textContent = '正在跳转到评教页面...'; window.location.href = 'https://bkjwtest.guet.edu.cn/student/for-std/extra-system/student-summation-forstudent/index'; }); document.getElementById('guet-go-textbook').addEventListener('click', () => { statusEl.textContent = '正在跳转到教材评价...'; window.location.href = 'https://bkjwtest.guet.edu.cn/student/for-std/textbook-evaluate'; }); setupMinimize(panel); setupDrag(panel); } // 最小化功能通用函数 function setupMinimize(panel) { let minimized = false; const minimizeBtn = panel.querySelector('.guet-eval-minimize'); const content = panel.querySelectorAll('label, span, input, textarea, .guet-eval-btn, .guet-eval-status, .guet-eval-info, .guet-eval-divider, .guet-eval-progress, .guet-eval-author'); minimizeBtn.addEventListener('click', () => { minimized = !minimized; content.forEach(el => { el.style.display = minimized ? 'none' : ''; }); minimizeBtn.textContent = minimized ? '+' : '−'; panel.querySelector('h3').style.marginBottom = minimized ? '0' : '12px'; }); } // 拖动功能通用函数 function setupDrag(panel) { let isDragging = false; let startX, startY, initialX, initialY; panel.addEventListener('mousedown', (e) => { // 如果点击的是输入框、按钮等,不触发拖动 if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'BUTTON' || e.target.closest('button')) { return; } isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; // 切换到 left/top 定位 panel.style.right = 'auto'; panel.style.left = initialX + 'px'; panel.style.top = initialY + 'px'; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; let newX = initialX + deltaX; let newY = initialY + deltaY; // 限制在窗口范围内 const maxX = window.innerWidth - panel.offsetWidth; const maxY = window.innerHeight - panel.offsetHeight; newX = Math.max(0, Math.min(newX, maxX)); newY = Math.max(0, Math.min(newY, maxY)); panel.style.left = newX + 'px'; panel.style.top = newY + 'px'; }); document.addEventListener('mouseup', () => { isDragging = false; }); } // 创建评教页面面板(原有的面板) function createEvalPanel() { // 移除已存在的面板 if (document.querySelector('.guet-eval-panel')) { document.querySelector('.guet-eval-panel').remove(); } // 加载保存的评语 const savedComments = loadSavedComments(); const savedMinScore = GM_getValue('minScore', 8); // 检查是否在自动循环模式 const isAutoMode = GM_getValue('autoLoopEnabled', false); // 检测当前是评教表单页面还是老师列表页面 const isFormPage = isEvalFormPage(); const isListPage = isTeacherListPage(); const panel = document.createElement('div'); panel.className = 'guet-eval-panel'; panel.innerHTML = `

🎓 GUET评教助手

By Deng Kai
仅供学习交流,请勿用于商业用途
使用方法进QQ群自取:1081070075
📊 评分规则:自动检测满分,随机选择高分
📝 问题13自动选"是",问题14自动选"优秀"
满分10分时的下限,满分15分时自动调整为≥11 修改后点击应用,下次自动使用 修改后点击应用,下次自动使用
${isAutoMode ? '🔄 自动模式运行中...' : '准备就绪'}
`; document.body.appendChild(panel); // 绑定事件 const statusEl = document.getElementById('guet-status'); const autoLoopBtn = document.getElementById('guet-auto-loop'); const stopLoopBtn = document.getElementById('guet-stop-loop'); // 如果是自动模式 if (isAutoMode) { autoLoopBtn.style.display = 'none'; stopLoopBtn.style.display = 'block'; autoLoopEnabled = true; // 根据页面类型自动执行 setTimeout(async () => { if (isFormPage) { // 在评教表单页面,自动填写并提交 statusEl.textContent = '📝 自动模式:开始填写...'; autoLoopInProgress = true; await autoFillAndSubmit(statusEl); } else if (isListPage) { // 在老师列表页面,先刷新列表再点击下一个老师 statusEl.textContent = '🔄 刷新列表...'; // 等待列表刷新 await delay(1000); statusEl.textContent = '🔍 自动模式:查找下一个老师...'; const found = await clickNextTeacher(); if (!found) { stopAutoLoop(statusEl, '🎉 所有老师评教完成!'); } } }, 2000); // 监听窗口获得焦点(当评教标签页关闭后,列表页面会获得焦点) if (isListPage) { window.addEventListener('focus', async function onFocus() { if (!GM_getValue('autoLoopEnabled', false)) { window.removeEventListener('focus', onFocus); return; } console.log('[评教助手] 窗口获得焦点,刷新并继续...'); statusEl.textContent = '🔄 检测到返回,刷新列表...'; // 刷新页面以更新列表 await delay(1000); location.reload(); }); } } // 应用上述内容(保存评语和评分到本地) document.getElementById('guet-save-comments').addEventListener('click', () => { const courseComment = document.getElementById('guet-course-comment').value; const teacherComment = document.getElementById('guet-teacher-comment').value; const minScore = parseInt(document.getElementById('guet-min-score').value) || 8; if (minScore < 1 || minScore > 9) { statusEl.textContent = '❌ 分数下限需要在1-9之间'; return; } if (courseComment.length < 50 || courseComment.length > 500) { statusEl.textContent = '❌ 课程评语需要50-500字'; return; } if (teacherComment.length < 5 || teacherComment.length > 100) { statusEl.textContent = '❌ 老师评语需要5-100字'; return; } saveComments(courseComment, teacherComment); GM_setValue('minScore', minScore); statusEl.textContent = '✅ 内容已保存到本地!'; }); // 一键填写 document.getElementById('guet-fill-all').addEventListener('click', () => { const courseComment = document.getElementById('guet-course-comment').value || savedComments.courseComment; const teacherComment = document.getElementById('guet-teacher-comment').value || savedComments.teacherComment; const minScore = parseInt(document.getElementById('guet-min-score').value) || 8; if (minScore < 1 || minScore > 9) { statusEl.textContent = '❌ 分数下限需要在1-9之间'; return; } fillAll(courseComment, teacherComment, minScore, statusEl); }); // 自动提交 document.getElementById('guet-submit').addEventListener('click', () => { autoSubmit(statusEl); }); // 启动自动循环评教 autoLoopBtn.addEventListener('click', () => { // 检查是否首次使用,显示弹窗权限提示 const hasShownPopupTip = GM_getValue('hasShownPopupTip', false); if (!hasShownPopupTip) { const confirmed = confirm( '⚠️ 重要提示:自动循环评教需要打开新标签页\n\n' + '请先设置浏览器允许弹窗:\n' + '1. 点击地址栏的 🔒 或 ⓘ 图标\n' + '2. 找到「弹出窗口和重定向」或「弹出式窗口」\n' + '3. 设置为「允许」\n' + '4. 刷新页面后重新点击启动按钮\n\n' + '或在浏览器设置中将以下网址添加到允许列表:\n' + 'https://bkjwtest.guet.edu.cn\n\n' + '已完成设置?点击确定继续' ); if (!confirmed) { return; } GM_setValue('hasShownPopupTip', true); } autoLoopBtn.style.display = 'none'; stopLoopBtn.style.display = 'block'; startAutoLoop(statusEl); }); // 停止自动循环 stopLoopBtn.addEventListener('click', () => { stopLoopBtn.style.display = 'none'; autoLoopBtn.style.display = 'block'; stopAutoLoop(statusEl); }); // 最小化功能 setupMinimize(panel); // 拖动功能 setupDrag(panel); } // ==================== 教材评价功能 ==================== // 默认教材评语 const DEFAULT_TEXTBOOK_COMMENT = '教材内容丰富,印刷质量好,非常适合学习使用。'; // 检测是否在教材评价表单页面 function isTextbookFormPage() { return window.location.href.includes('/textbook-evaluate/evaluate?'); } // 检测是否在教材评价列表页面 function isTextbookListPage() { return window.location.href.includes('/textbook-evaluate/search-index/') || (window.location.href.includes('/textbook-evaluate') && !isTextbookFormPage()); } // 获取待评价教材列表 function getPendingTextbooks() { const rows = document.querySelectorAll('table tbody tr'); const pending = []; rows.forEach((row, index) => { const cells = row.querySelectorAll('td'); if (cells.length >= 4) { const actionCell = cells[cells.length - 1]; const evalSpan = actionCell.querySelector('span.text-primary'); if (evalSpan && evalSpan.textContent.trim() === '评价') { const courseName = cells[0]?.textContent?.trim() || `教材${index + 1}`; pending.push({ row, evalSpan, courseName }); } } }); return pending; } // 填写教材评价表单(10个单选题 + 1个文本框) async function fillTextbookForm(statusEl) { // 选择所有10分选项 const radios = document.querySelectorAll('input[type="radio"]'); const groups = {}; radios.forEach(r => { if (!groups[r.name]) groups[r.name] = []; groups[r.name].push(r); }); let clickedCount = 0; Object.values(groups).forEach(group => { const tenPoint = group.find(r => r.value === '10'); if (tenPoint) { tenPoint.click(); clickedCount++; } }); // 填写意见和建议 const textarea = document.querySelector('textarea'); if (textarea) { const comment = GM_getValue('textbookComment', DEFAULT_TEXTBOOK_COMMENT); textarea.value = comment; textarea.dispatchEvent(new Event('input', { bubbles: true })); } if (statusEl) statusEl.textContent = `✅ 已填写 ${clickedCount} 个评分`; return clickedCount; } // 提交教材评价 async function submitTextbookForm(statusEl) { const submitBtn = document.querySelector('button'); if (submitBtn && submitBtn.textContent.includes('提交')) { submitBtn.click(); if (statusEl) statusEl.textContent = '✅ 已提交'; return true; } // 备用:查找所有按钮 const buttons = document.querySelectorAll('button'); for (const btn of buttons) { if (btn.textContent.trim() === '提交') { btn.click(); if (statusEl) statusEl.textContent = '✅ 已提交'; return true; } } if (statusEl) statusEl.textContent = '❌ 未找到提交按钮'; return false; } // 自动评价所有教材 async function autoEvaluateAllTextbooks(statusEl) { const pending = getPendingTextbooks(); if (pending.length === 0) { statusEl.textContent = '✅ 没有待评价的教材'; return; } statusEl.textContent = `📚 发现 ${pending.length} 本待评价教材,开始评价...`; GM_setValue('textbookAutoMode', true); GM_setValue('textbookPendingCount', pending.length); await delay(500); // 点击第一个评价按钮 pending[0].evalSpan.click(); } // 创建教材评价面板 function createTextbookPanel() { if (document.querySelector('.guet-eval-panel')) { document.querySelector('.guet-eval-panel').remove(); } const isFormPage = isTextbookFormPage(); const isAutoMode = GM_getValue('textbookAutoMode', false); const panel = document.createElement('div'); panel.className = 'guet-eval-panel'; if (isFormPage) { // 评价表单页面 panel.innerHTML = `

📚 教材评价助手

By Deng Kai
仅供学习交流,请勿用于商业用途
📝 10个评分题自动选10分
💬 自动填写意见和建议
${isAutoMode ? '🔄 自动模式运行中...' : '准备就绪'}
`; } else { // 列表页面 panel.innerHTML = `

📚 教材评价助手

By Deng Kai
仅供学习交流,请勿用于商业用途
📖 在页面上选择学期后点击检查
🚀 一键完成所有教材评价
${isAutoMode ? '🔄 自动模式运行中...' : '⬆️ 请先在网页中选择当前学期'}
`; } document.body.appendChild(panel); const statusEl = document.getElementById('guet-status'); if (isFormPage) { // 表单页面事件 document.getElementById('guet-save-textbook-comment').addEventListener('click', () => { const comment = document.getElementById('guet-textbook-comment').value; GM_setValue('textbookComment', comment); statusEl.textContent = '✅ 评语已保存'; }); document.getElementById('guet-fill-textbook').addEventListener('click', async () => { statusEl.textContent = '📝 正在填写...'; await fillTextbookForm(statusEl); }); document.getElementById('guet-submit-textbook').addEventListener('click', async () => { statusEl.textContent = '🚀 正在提交...'; await submitTextbookForm(statusEl); }); // 自动模式:自动填写并提交 if (isAutoMode) { setTimeout(async () => { statusEl.textContent = '📝 自动填写中...'; await fillTextbookForm(statusEl); await delay(800); statusEl.textContent = '🚀 自动提交中...'; await submitTextbookForm(statusEl); }, 1500); } } else { // 列表页面事件 const checkBtn = document.getElementById('guet-check-textbooks'); const startBtn = document.getElementById('guet-start-textbook-eval'); const stopBtn = document.getElementById('guet-stop-textbook'); // 保存评语 document.getElementById('guet-save-textbook-comment').addEventListener('click', () => { const comment = document.getElementById('guet-textbook-comment').value; GM_setValue('textbookComment', comment); statusEl.textContent = '✅ 评语已保存'; }); checkBtn.addEventListener('click', () => { const pending = getPendingTextbooks(); if (pending.length === 0) { statusEl.textContent = '✅ 当前学期没有待评价教材'; startBtn.style.display = 'none'; } else { statusEl.textContent = `📚 发现 ${pending.length} 本待评价教材`; startBtn.style.display = 'block'; } }); startBtn.addEventListener('click', async () => { startBtn.style.display = 'none'; stopBtn.style.display = 'block'; await autoEvaluateAllTextbooks(statusEl); }); stopBtn.addEventListener('click', () => { GM_setValue('textbookAutoMode', false); stopBtn.style.display = 'none'; startBtn.style.display = 'none'; checkBtn.style.display = 'block'; statusEl.textContent = '⏹️ 已停止自动评价'; }); // 自动模式:继续评价下一本 if (isAutoMode) { setTimeout(() => { const pending = getPendingTextbooks(); if (pending.length === 0) { GM_setValue('textbookAutoMode', false); statusEl.textContent = '🎉 所有教材评价完成!'; stopBtn.style.display = 'none'; } else { statusEl.textContent = `📚 还有 ${pending.length} 本待评价,继续...`; pending[0].evalSpan.click(); } }, 2000); } } setupMinimize(panel); setupDrag(panel); } // ==================== 初始化 ==================== function init() { // 移除已存在的面板(页面切换时) const existingPanel = document.querySelector('.guet-eval-panel'); if (existingPanel) { existingPanel.remove(); } const pageType = getPageType(); console.log('[评教助手] 当前页面类型:', pageType); switch (pageType) { case 'login': // 登录页面:显示登录面板 setTimeout(createLoginPanel, 1000); break; case 'home': // 首页:显示导航面板 setTimeout(createHomePanel, 1500); break; case 'evaluation': // 评教页面:显示评教面板 setTimeout(createEvalPanel, 1500); break; case 'textbook-evaluate': // 教材评价页面:显示教材评价面板 setTimeout(createTextbookPanel, 1500); break; default: // 其他页面:不显示面板 console.log('[评教助手] 非目标页面,不显示面板'); } } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // 监听URL变化(SPA应用) let lastUrl = location.href; let lastPageType = getPageType(); // 处理页面变化(用于自动循环模式) async function handlePageChange() { const isAutoMode = GM_getValue('autoLoopEnabled', false); if (isAutoMode && autoLoopEnabled) { console.log('[评教助手] 自动模式:检测页面变化...'); await delay(2000); // 等待页面加载 const statusEl = document.getElementById('guet-status'); if (isTeacherListPage()) { // 返回到老师列表页面,查找下一个未评教的老师 console.log('[评教助手] 自动模式:检测到老师列表页面'); if (statusEl) statusEl.textContent = '🔍 查找下一个未评教老师...'; const found = await clickNextTeacher(); if (!found) { stopAutoLoop(statusEl, '🎉 所有老师评教完成!'); } } else if (isEvalFormPage()) { // 进入评教表单页面,自动填写 console.log('[评教助手] 自动模式:检测到评教表单页面'); if (statusEl) statusEl.textContent = '📝 开始自动填写...'; autoLoopInProgress = true; await autoFillAndSubmit(statusEl); } } } // 使用多种方式监听URL变化 new MutationObserver(() => { const url = location.href; const currentPageType = getPageType(); // URL变化或页面类型变化时重新初始化 if (url !== lastUrl || currentPageType !== lastPageType) { lastUrl = url; lastPageType = currentPageType; console.log('[评教助手] 检测到页面变化,重新初始化...'); // 先移除旧面板 const oldPanel = document.querySelector('.guet-eval-panel'); if (oldPanel) oldPanel.remove(); setTimeout(() => { init(); // 自动模式下处理页面变化 handlePageChange(); }, 800); } }).observe(document, { subtree: true, childList: true }); // 监听 hashchange 和 popstate 事件 window.addEventListener('hashchange', () => { console.log('[评教助手] hashchange 事件'); const oldPanel = document.querySelector('.guet-eval-panel'); if (oldPanel) oldPanel.remove(); setTimeout(() => { init(); handlePageChange(); }, 800); }); window.addEventListener('popstate', () => { console.log('[评教助手] popstate 事件'); const oldPanel = document.querySelector('.guet-eval-panel'); if (oldPanel) oldPanel.remove(); setTimeout(() => { init(); handlePageChange(); }, 800); }); })();