可以试试这个?
Pending#1514叶屿Created at 3 months ago

// ==UserScript==
// @name         兰大改
// @namespace    https://github.com/DonnyRe/LZUAutoEvaluation
// @version      1.0.0
// @description  实现自动评教、可选的自动提交的自动化操作,省时省力,老师们也辛苦了默认最高好评。
// @author       Donny
// @license      GPL-3.0-or-later
// @match        *://jwqe.lzu.edu.cn:8080/*
// @match        *://my.lzu.edu.cn/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==


(function() {     'use strict';
    // ========== 配置区域 ==========     const CONFIG = {         AUTO\_SUBMIT: true,         LOG\_DETAIL: true,         DEFAULT\_SCORE\_LEVEL: 100 // 默认评分等级:100, 95, 90     };
    // ---------- 工具函数 ----------     const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));     const log = (...args) => CONFIG.LOG\_DETAIL && console.log('[评教]', ...args);     const logImportant = (...args) => console.log('[评教]', ...args);
    // 状态管理     function getAutoSubmitState() {         if (typeof GM\_getValue !== 'undefined') {             return GM\_getValue('AUTO\_SUBMIT', CONFIG.AUTO\_SUBMIT);         }         const saved = localStorage.getItem('LZU\_AUTO\_SUBMIT\_FINAL');         return saved !== null ? JSON.parse(saved) : CONFIG.AUTO\_SUBMIT;     }     function setAutoSubmitState(state) {         if (typeof GM\_setValue !== 'undefined') {             GM\_setValue('AUTO\_SUBMIT', state);         } else {             localStorage.setItem('LZU\_AUTO\_SUBMIT\_FINAL', JSON.stringify(state));         }         updateToggleButton(state);     }     function getScoreLevel() {         if (typeof GM\_getValue !== 'undefined') {             return GM\_getValue('SCORE\_LEVEL', CONFIG.DEFAULT\_SCORE\_LEVEL);         }         const saved = localStorage.getItem('LZU\_SCORE\_LEVEL');         return saved !== null ? parseInt(saved) : CONFIG.DEFAULT\_SCORE\_LEVEL;     }     function setScoreLevel(level) {         if (typeof GM\_setValue !== 'undefined') {             GM\_setValue('SCORE\_LEVEL', level);         } else {             localStorage.setItem('LZU\_SCORE\_LEVEL', level.toString());         }         updateScoreLevelButton(level);     }
    // ---------- 1. 创建控制按钮 (防重复) ----------     let buttonsCreated = false;
    function updateToggleButton(autoSubmitState) {         const toggleBtn = document.getElementById('toggleAutoSubmitBtn');         if (toggleBtn) {             toggleBtn.textContent = autoSubmitState ? '✅ 自动提交:开' : '⏸️ 自动提交:关';             toggleBtn.style.background = autoSubmitState ?                 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)' :                 'linear-gradient(135deg, #757575 0%, #424242 100%)';         }     }
    function updateScoreLevelButton(level) {         const scoreBtn = document.getElementById('scoreLevelBtn');         if (scoreBtn) {             scoreBtn.textContent = \`📊 评分: ${level}分\`;         }     }
    function createControlButtons() {         if \(buttonsCreated \|\| document\.getElementById\('lzu\-eval\-controls'\)\) \{             log('按钮已存在,跳过创建。');             return;         }         buttonsCreated = true;         logImportant('正在创建控制按钮...');
        const container = document.createElement('div');         container.id = 'lzu-eval-controls';         container.style.cssText = \`             position: fixed !important;             top: 20px !important;             right: 20px !important;             z-index: 2147483647 !important;             display: flex !important;             flex-direction: column !important;             gap: 12px !important;             align-items: flex-end !important;             font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif !important;         \`;
        // 主按钮         const mainBtn = document.createElement('button');         mainBtn.id = 'mainEvaluateBtn';         mainBtn.textContent = '🚀 开始自动评教';         mainBtn.style.cssText = \`             padding: 14px 28px !important;             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;             color: white !important;             border: none !important;             border-radius: 30px !important;             font-size: 16px !important;             font-weight: bold !important;             cursor: pointer !important;             box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;             transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;             min-width: 200px !important;             font-family: 'Microsoft YaHei', sans-serif !important;             text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;         \`;
        // 切换按钮         const toggleBtn = document.createElement('button');         toggleBtn.id = 'toggleAutoSubmitBtn';         const autoSubmitState = getAutoSubmitState();         toggleBtn.textContent = autoSubmitState ? '✅ 自动提交:开' : '⏸️ 自动提交:关';         toggleBtn.style.cssText = \`             padding: 10px 20px !important;             background: ${autoSubmitState ?                 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)' :                 'linear-gradient(135deg, #bdc3c7 0%, #95a5a6 100%)'} !important;             color: white !important;             border: none !important;             border-radius: 25px !important;             font-size: 14px !important;             font-weight: 600 !important;             cursor: pointer !important;             box-shadow: 0 4px 15px ${autoSubmitState ? 'rgba(17, 153, 142, 0.3)' : 'rgba(0, 0, 0, 0.1)'} !important;             transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;             min-width: 160px !important;             font-family: 'Microsoft YaHei', sans-serif !important;         \`;
        // 评分等级面板         const scorePanel = document.createElement('div');         scorePanel.id = 'scoreLevelPanel';         scorePanel.style.cssText = \`             background: rgba(255, 255, 255, 0.98) !important;             backdrop-filter: blur(10px) !important;             border-radius: 16px !important;             padding: 16px !important;             box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12) !important;             border: 1px solid rgba(255, 255, 255, 0.8) !important;             min-width: 200px !important;         \`;
        const panelTitle = document.createElement('div');         panelTitle.textContent = '📊 评分等级';         panelTitle.style.cssText = \`             font-size: 15px !important;             font-weight: bold !important;             color: #333 !important;             margin-bottom: 12px !important;             padding-bottom: 8px !important;             border-bottom: 2px solid #f0f0f0 !important;         \`;         scorePanel.appendChild(panelTitle);
        const currentScore = getScoreLevel();         const scoreOptions = [             { value: 100, label: '100分', desc: '全部最高分', color: '#4CAF50' },             { value: 95, label: '95分', desc: '90%最高 + 10%次高', color: '#FF9800' },             { value: 90, label: '90分', desc: '80%最高 + 20%次高', color: '#2196F3' }         ];
        scoreOptions.forEach(option => {             const optionDiv = document.createElement('div');             optionDiv.style.cssText = \`                 display: flex !important;                 align-items: center !important;                 padding: 10px !important;                 margin: 6px 0 !important;                 border-radius: 10px !important;                 cursor: pointer !important;                 transition: all 0.2s ease !important;                 background: ${currentScore === option.value ? 'rgba(33, 150, 243, 0.08)' : 'transparent'} !important;                 border: 2px solid ${currentScore === option.value ? option.color : 'transparent'} !important;             \`;
            const radio = document.createElement('input');             radio.type = 'radio';             radio.name = 'scoreLevel';             radio.value = option.value;             radio.checked = currentScore === option.value;             radio.style.cssText = \`                 width: 18px !important;                 height: 18px !important;                 margin-right: 10px !important;                 cursor: pointer !important;                 accent-color: ${option.color} !important;             \`;
            const labelDiv = document.createElement('div');             labelDiv.style.cssText = \`flex: 1 !important;\`;
            const labelText = document.createElement('div');             labelText.textContent = option.label;             labelText.style.cssText = \`                 font-size: 14px !important;                 font-weight: 600 !important;                 color: #333 !important;                 margin-bottom: 2px !important;             \`;
            const descText = document.createElement('div');             descText.textContent = option.desc;             descText.style.cssText = \`                 font-size: 12px !important;                 color: #666 !important;             \`;
            labelDiv.appendChild(labelText);             labelDiv.appendChild(descText);             optionDiv.appendChild(radio);             optionDiv.appendChild(labelDiv);
            // 悬停效果             optionDiv.onmouseover = () => {                 if (currentScore !== option.value) {                     optionDiv.style.background = 'rgba(0, 0, 0, 0.03)';                 }             };             optionDiv.onmouseout = () => {                 if (currentScore !== option.value) {                     optionDiv.style.background = 'transparent';                 }             };
            // 点击事件             optionDiv.onclick = () => {                 setScoreLevel(option.value);                 logImportant(\`评分等级已切换为: ${option.value}分\`);                 // 重新创建面板以更新选中状态                 container.innerHTML = '';                 buttonsCreated = false;                 createControlButtons();             };
            scorePanel.appendChild(optionDiv);         });
        // 按钮交互         [mainBtn, toggleBtn].forEach(btn => {             btn.onmouseover = () => btn.style.transform = 'scale(1.05)';             btn.onmouseout = () => btn.style.transform = 'scale(1)';         });
        // 主按钮点击事件         mainBtn.addEventListener('click', () => {             mainBtn.disabled = true;             const originalText = mainBtn.textContent;             mainBtn.textContent = '⏳ 执行中...';             autoEvaluateForNewPage().finally(() => {                 setTimeout(() => {                     mainBtn.disabled = false;                     mainBtn.textContent = originalText;                 }, 3000);             });         });
        // 切换按钮点击事件         toggleBtn.addEventListener('click', () => {             const currentState = getAutoSubmitState();             const newState = !currentState;             setAutoSubmitState(newState);             logImportant(\`自动提交已${newState ? '开启' : '关闭'}\`);         });
        container.appendChild(mainBtn);         container.appendChild(scorePanel);         container.appendChild(toggleBtn);         document.body.appendChild(container);         logImportant('控制按钮创建完成。');     }
    // ---------- 2. 核心评教功能 ----------     async function fillTextEvaluations() {         logImportant('开始处理文字评价...');         const textAreas = document.querySelectorAll('textarea.uni-textarea-textarea');         if (textAreas.length === 0) {             log('未找到文字评价输入框。');             return;         }         logImportant(\`找到 ${textAreas.length} 个输入框。\`);
        const positiveComments = [             "老师授课准备充分,讲解清晰生动,重点突出,课堂节奏把握得很好。",             "课程内容充实,理论与实践结合紧密,对启发思维和掌握知识很有帮助。",             "课堂氛围融洽,师生互动积极,能鼓励学生提出问题并耐心解答。",             "教学态度认真负责,对课程内容掌握深入,能感受到老师对教学的热情。",             "本学期收获很大,感谢老师的辛勤付出,希望后续课程能继续保持。",             "课程组织有序,作业和考核方式合理,能有效检验学习成果。",         ];
        const usedIndices = new Set();         for (let i = 0; i < textAreas.length; i++) {             const textArea = textAreas[i];             textArea.scrollIntoView({ behavior: 'smooth', block: 'center' });             await sleep(400 + Math.random() \* 600);             textArea.focus();             await sleep(200);
            let randomIndex;             do {                 randomIndex = Math.floor(Math.random() \* positiveComments.length);                 if (usedIndices.size >= positiveComments.length \* 0.7) usedIndices.clear();             } while (usedIndices.has(randomIndex));
            usedIndices.add(randomIndex);             const comment = positiveComments[randomIndex];             textArea.value = comment;             textArea.dispatchEvent(new Event('input', { bubbles: true }));             textArea.dispatchEvent(new Event('change', { bubbles: true }));             log(\`第 ${i + 1} 个输入框: ${comment.substring(0, 20)}...\`);             textArea.blur();             await sleep(300);         }         logImportant('文字评价填写完成。');     }
    // ---------- 3. 【关键修改】精准提交函数 ----------     async function submitEvaluation() {         const autoSubmit = getAutoSubmitState();         if (!autoSubmit) {             logImportant('自动提交已关闭,请手动提交。');             return false;         }
        logImportant('正在查找提交按钮...');         await sleep(1000); // 等待页面稳定
        // 首要策略:精准查找您提供的 uni-view.box3-1 元素         let submitButton = document.querySelector('uni-view.box3-1');
        // 备用策略:如果精准查找失败,尝试其他常见选择器         if (!submitButton) {             log('精准查找失败,尝试备用选择器...');             const backupSelectors = [                 'uni-view:contains("提交")',                 '.box3-1',                 '[class\*="submit"]',                 'button:contains("提交评价")',                 'button:contains("提交")',                 'button:contains("确定")'             ];             for (const selector of backupSelectors) {                 submitButton = document.querySelector(selector);                 if (submitButton) {                     logImportant(\`通过备用选择器找到: ${selector}\`);                     break;                 }             }         }
        if (submitButton) {             logImportant(\`✅ 找到提交按钮,开始提交... (元素: ${submitButton.tagName}.${submitButton.className})\`);             submitButton.scrollIntoView({ behavior: 'smooth', block: 'center' });             await sleep(800);             submitButton.focus();             submitButton.click();             logImportant('已点击提交按钮。');
            // 处理可能的二次确认弹窗             await sleep(1500);             const confirmSubmitted = await handleConfirmationDialog();             if (confirmSubmitted) {                 logImportant('✅ 自动提交流程完成。');             }             return true;         } else {             logImportant('⚠️ 未找到提交按钮,请手动提交。');             // 在控制台输出提示,便于用户手动操作             console.warn('请手动查找并点击页面上的提交按钮。');             return false;         }     }
    // 处理二次确认对话框     async function handleConfirmationDialog() {         log('检查二次确认对话框...');         await sleep(1000);         // 常见的确认按钮选择器         const confirmSelectors = [             '.confirm',             '.btn-confirm',             'uni-view:contains("确定")',             'button:contains("确定")',             '.el-button--primary'         ];         for (const selector of confirmSelectors) {             const confirmBtn = document.querySelector(selector);             if (confirmBtn) {                 const style = window.getComputedStyle(confirmBtn);                 if (style.display !== 'none' && style.visibility !== 'hidden') {                     confirmBtn.click();                     log('✅ 已点击确认按钮。');                     await sleep(500);                     return true;                 }             }         }         log('未检测到二次确认对话框。');         return true; // 没有确认对话框也视为成功     }
    // ---------- 4. 自动评教主流程 ----------     async function autoEvaluateForNewPage() {         const autoSubmit = getAutoSubmitState();         const scoreLevel = getScoreLevel();         logImportant('🚀 开始自动评教...');         logImportant(\`模式: 自动提交 ${autoSubmit ? '开启' : '关闭'}, 评分等级: ${scoreLevel}分\`);
        // 根据评分等级选择目标选项         const optionSets = {             100: [['完全符合', '优秀', 'A']],             95: [['完全符合', '优秀', 'A'], ['符合', '良好', 'B']],             90: [['完全符合', '优秀', 'A'], ['符合', '良好', 'B']]         };         const availableOptions = optionSets\[scoreLevel\] \|\| optionSets\[100\];
        // 计算选项分配比例         const getTargetOptions = (questionIndex) => {             if (scoreLevel === 100) {                 return availableOptions[0]; // 全部最高分             } else if (scoreLevel === 95) {                 // 90%最高分,10%次高分                 return Math.random() < 0.9 ? availableOptions[0] : availableOptions[1];             } else if (scoreLevel === 90) {                 // 80%最高分,20%次高分                 return Math.random() < 0.8 ? availableOptions[0] : availableOptions[1];             }             return availableOptions[0];         };
        // 定位题目容器         let questionContainers = document.querySelectorAll('[class\*="question"], [class\*="item"], .el-form-item, .uni-list-cell');         if (questionContainers.length === 0) {             log('尝试智能回溯寻找题目容器...');             const allOptionElements = document.querySelectorAll('uni-view');             const parentSet = new Set();             allOptionElements.forEach(el => {                 let parent = el.parentElement;                 for (let i = 0; i < 4 && parent; i++) {                     if (parent.classList && parent.classList.length > 0) {                         parentSet.add(parent);                         break;                     }                     parent = parent.parentElement;                 }             });             questionContainers = Array.from(parentSet);         }         logImportant(\`识别出 ${questionContainers.length} 个题目区域。\`);
        // 处理选择题         let clickedCount = 0;         for (const container of questionContainers) {             let clicked = false;             const targetOptions = getTargetOptions(clickedCount);             for (const targetText of targetOptions) {                 const elements = container.querySelectorAll('uni-view, span, div, label');                 for (let el of elements) {                     if (el.textContent && el.textContent.trim() === targetText) {                         el.scrollIntoView({ behavior: 'smooth', block: 'center' });                         await sleep(300 + Math.random() \* 400);                         el.click();                         log(\`题目${clickedCount + 1}: ✅ "${targetText}"\`);                         clickedCount++;                         clicked = true;                         await sleep(150);                         break;                     }                 }                 if (clicked) break;             }         }         logImportant(\`选择题完成 ${clickedCount} 题。\`);
        // 处理文字评价         await fillTextEvaluations();
        // 执行提交         const submitted = await submitEvaluation();         if (submitted) {             logImportant('✨ 自动评教流程执行完毕!');         } else {             logImportant('✨ 内容已填写完成,请根据控制台提示操作。');         }     }
    // ---------- 5. 页面初始化 ----------     function initialize() {         logImportant('脚本初始化...');         // 简单检测:页面是否有 uni-view 元素         if (document.querySelector('uni-view')) {             setTimeout(createControlButtons, 1000);         } else {             // 如果没有,等待一下再尝试(应对动态加载)             const observer = new MutationObserver((mutations, obs) => {                 if (document.querySelector('uni-view')) {                     obs.disconnect();                     setTimeout(createControlButtons, 500);                 }             });             observer.observe(document.body, { childList: true, subtree: true });             // 5秒后超时停止观察             setTimeout(() => observer.disconnect(), 5000);         }     }
    // 启动     if (document.readyState === 'loading') {         document.addEventListener('DOMContentLoaded', initialize);     } else {         initialize();     }
})();
No data
Login to comment
Labels
Watch
Participants