import React, { useState } from 'react'; import { createRoot } from 'react-dom/client'; import { Check, Copy, ShieldAlert, Zap, Code2, Star, Download } from 'lucide-react'; const OPTIMIZED_SCRIPT = `// ==UserScript== // @name 中南林一键自动教评(全能稳定版 - 核心优化) // @namespace https://github.com/jinnianliuxing/ // @version 2.3.0 // @description 智能自动评价:React/Vue兼容修复、黑名单跳过、网络断连保护、随机拟人延迟 // @author IKUN-91-张 & AI助手 // @match https://jxzlpt.csuft.edu.cn/* // @match *://jxzlpt-443.webvpn.csuft.edu.cn/* // @match https://https-jxzlpt-csuft-edu-cn-443.webvpn.csuft.edu.cn/* // @icon https://raw.githubusercontent.com/jinnianliuxing/my-script-icons/refs/heads/main/IMG_202507232298_120x120.png // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ================= 配置区域 ================= const CONFIG = { VERSION: "3.1.0", // 基础延迟 (毫秒) DELAY: { SCAN: 1500, // 扫描列表间隔 DIALOG_WAIT: 2000,// 等待弹窗最大时长 FILL: 800, // 填表思考时间 SUBMIT: 1500, // 提交前停顿 NEXT: 2000 // 下一个课程间隔 }, JITTER: 0.4, // 随机波动幅度 40% DEFAULT_COMMENT: [ '老师备课充分,授课重点突出,条理清晰。', '教学内容丰富,课堂气氛活跃,互动良好。', '讲解深入浅出,能有效引导学生思考。', '课程设计合理,理论联系实际,收获很大。', '老师治学严谨,对学生要求严格,负责任。' ] }; // ================= 全局状态 ================= const STATE = { isRunning: false, isPaused: false, failedIds: new Set(), currentId: null, ui: null }; // ================= 核心工具函数 ================= // 1. 异步休眠 (支持随机波动) const sleep = (ms) => { const jitter = ms * CONFIG.JITTER; const finalMs = ms + (Math.random() * jitter * 2 - jitter); return new Promise(resolve => setTimeout(resolve, Math.max(200, finalMs))); }; // 2. 查找可见元素 (兼容多种框架) const findVisible = (selector) => { const els = Array.from(document.querySelectorAll(selector)); return els.find(el => el.offsetParent !== null && window.getComputedStyle(el).display !== 'none'); }; // 3. 等待元素出现 const waitFor = async (selector, timeout = 3000) => { const start = Date.now(); while (Date.now() - start < timeout) { const el = findVisible(selector); if (el) return el; await sleep(200); } return null; }; // 4. React/Vue 兼容的输入框赋值 (关键修复) const setNativeValue = (element, value) => { const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set; const prototype = Object.getPrototypeOf(element); const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set; if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { prototypeValueSetter.call(element, value); } else { valueSetter.call(element, value); } element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); element.dispatchEvent(new Event('blur', { bubbles: true })); }; // 5. 智能识别满分 const detectMaxScore = (input) => { try { // 向上查找父容器 (行或表单项) const container = input.closest('tr') || input.closest('.el-form-item') || input.parentElement; if (!container) return 10; const text = container.innerText; // 匹配 "满分: 10", "10分", "(10)" 等模式 const match = text.match(/(\d+)分/) || text.match(/满分[::]?\s*(\d+)/) || text.match(/\((\d+)\)/); if (match && match[1]) { return parseInt(match[1], 10); } } catch (e) { console.warn('识别分数失败', e); } return 10; // 默认兜底 }; // ================= 业务逻辑 ================= const performEvaluation = async () => { while (STATE.isRunning) { if (STATE.isPaused) { await sleep(1000); continue; } updateUI('正在扫描未评课程...'); // 1. 扫描 const allBtns = Array.from(document.querySelectorAll('.btn_theme, button')); const targetBtn = allBtns.find(btn => { if (btn.offsetParent === null) return false; // 不可见 const text = btn.innerText.trim(); const row = btn.closest('tr'); // 排除已评价和黑名单 if (text !== '评价' && text !== '评估') return false; if (row && row.innerText.includes('已评价')) return false; const id = btn.getAttribute('data-id') || (row && row.getAttribute('data-id')); if (id && STATE.failedIds.has(id)) return false; return true; }); if (!targetBtn) { updateUI('🎉 所有课程处理完毕!', 'success'); STATE.isRunning = false; break; } // 记录当前 ID const row = targetBtn.closest('tr'); STATE.currentId = targetBtn.getAttribute('data-id') || (row && row.getAttribute('data-id')) || 'unknown'; // 2. 点击评价 updateUI(\`准备评价课程 ID: \${STATE.currentId}\`); targetBtn.click(); // 3. 等待弹窗 const dialog = await waitFor('.el-dialog, .modal-content, div[role="dialog"]', CONFIG.DELAY.DIALOG_WAIT + 1000); if (!dialog) { handleError('弹窗打开失败'); continue; } await sleep(CONFIG.DELAY.FILL); // 4. 填写表单 try { // 处理文本框/数字框 const inputs = Array.from(dialog.querySelectorAll('input[type="text"], input[type="number"]')) .filter(el => !el.readOnly && el.offsetParent); // 随机选一个扣分项 const randomDockIdx = inputs.length > 0 ? Math.floor(Math.random() * inputs.length) : -1; for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; const max = detectMaxScore(input); // 如果是大分值(>5),且命中扣分项,扣1分;否则满分 let score = max; if (max > 5 && i === randomDockIdx) score = max - 1; setNativeValue(input, score); await sleep(50); // 稍微间隔,显得真实 } // 处理单选框 (Radio) const radios = Array.from(dialog.querySelectorAll('input[type="radio"]')); // 简单的策略:分组点击第一个(通常是优秀) // 这里需要更复杂的逻辑来区分组,暂且假设点所有可见radio的每组第一个 // 更好的方式:查找 el-radio-group const radioGroups = dialog.querySelectorAll('.el-radio-group, .radio-list'); if (radioGroups.length > 0) { radioGroups.forEach(group => { const firstRadio = group.querySelector('input[type="radio"], .el-radio'); if(firstRadio) firstRadio.click(); }); } // 评语 const textarea = dialog.querySelector('textarea'); if (textarea) { const comment = CONFIG.DEFAULT_COMMENT[Math.floor(Math.random() * CONFIG.DEFAULT_COMMENT.length)]; setNativeValue(textarea, comment); } } catch (e) { handleError('填写表单出错: ' + e.message); closeDialog(); continue; } updateUI('填写完毕,准备提交...'); await sleep(CONFIG.DELAY.SUBMIT); // 5. 提交 const submitBtn = Array.from(dialog.querySelectorAll('button')).find(b => { const t = b.innerText.trim(); return t === '提交' || t === '保存' || t === '确定'; }); if (submitBtn) { submitBtn.click(); } else { handleError('未找到提交按钮'); closeDialog(); continue; } // 6. 验证结果 await sleep(CONFIG.DELAY.NEXT); const errorMsg = document.querySelector('.el-message--error, .alert-danger'); if (errorMsg) { handleError('提交后发现错误提示'); } else { // 假定成功,或者弹窗已关闭 updateUI('提交成功,休息一下...', 'success'); await sleep(1000); // 再次检查是否有未关闭的遮罩 closeDialog(); } } if (!STATE.isRunning) { if (STATE.ui.btn) { STATE.ui.btn.innerText = '▶ 开始评价'; STATE.ui.btn.style.background = '#4CAF50'; } } }; // ================= 辅助逻辑 ================= const handleError = (msg) => { console.warn(\`[Course \${STATE.currentId}] Error: \${msg}\`); STATE.failedIds.add(STATE.currentId); updateUI(\`出错跳过: \${msg}\`, 'error'); closeDialog(); }; const closeDialog = () => { // 尝试点击关闭按钮或按 ESC const closeBtns = document.querySelectorAll('.el-dialog__close, .close'); closeBtns.forEach(b => b.click()); }; const updateUI = (text, type = 'normal') => { if (!STATE.ui) return; const { statusEl } = STATE.ui; statusEl.innerText = text; statusEl.style.background = type === 'error' ? '#ffebee' : (type === 'success' ? '#e8f5e9' : '#f5f5f5'); statusEl.style.color = type === 'error' ? '#c62828' : (type === 'success' ? '#2e7d32' : '#333'); }; // ================= UI 初始化 ================= const initUI = () => { if (document.getElementById('csuft-helper-panel')) return; const panel = document.createElement('div'); panel.id = 'csuft-helper-panel'; panel.innerHTML = \`
这是一个经过优化的 UserScript,修复了 React/Vue 框架下的输入兼容性问题,并增加了拟人化操作和异常保护机制。
{OPTIMIZED_SCRIPT}
{desc}