// ==UserScript== // @name 正方教务教学评价 // @namespace https://scriptcat.org/ // @version 1.0.0 // @description 批量评价、双评语、随机防重复、自动关闭警告、抗检测 // @author 源 // @icon https://free.picui.cn/free/2025/12/18/6943e6aa2919b.ico // @match *://*/* // @grant none // @license MIT // ==/UserScript== (function () { const DEBUG_MODE = false; const VERSION = '1.0.0'; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); const $ = window.$; if (!$) { console.error('jQuery 未加载'); return; } const $1 = (sel, ctx) => (ctx || document).querySelector(sel); const $a = (sel, ctx) => [...(ctx || document).querySelectorAll(sel)]; const style = document.createElement('style'); style.textContent = ` /* 主面板:细腻毛玻璃 + 浅色渐变 */ .elegant-panel { position: fixed; left: 30px; top: 30px; z-index: 9999; width: 380px; max-height: calc(100vh - 60px); background: rgba(245, 247, 250, 0.85); backdrop-filter: blur(25px) saturate(150%); -webkit-backdrop-filter: blur(25px) saturate(150%); border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.8); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0,0,0,0.04); color: #1a1a2e; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; display: flex; flex-direction: column; overflow: hidden; transition: box-shadow 0.3s; } .elegant-panel:hover { box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0,0,0,0.06); } /* 暗色模式自动适配 */ @media (prefers-color-scheme: dark) { .elegant-panel { background: rgba(30, 35, 45, 0.8); color: #e8edf2; border-color: rgba(255,255,255,0.15); } .elegant-header { border-bottom-color: rgba(255,255,255,0.08); } .elegant-card { background: rgba(255,255,255,0.05); border-color: rgba(255,255,255,0.08); } .elegant-input, .elegant-textarea, .elegant-select { background: rgba(0,0,0,0.3); border-color: rgba(255,255,255,0.15); color: #f0f4f8; } } .elegant-header { display: flex; justify-content: space-between; align-items: center; padding: 18px 20px 14px; border-bottom: 1px solid rgba(0,0,0,0.06); cursor: move; flex-shrink: 0; } .elegant-title { font-size: 17px; font-weight: 650; letter-spacing: -0.2px; background: linear-gradient(135deg, #4F46E5, #7C3AED); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .elegant-sub { font-size: 11px; color: #6b7280; margin-top: 2px; font-weight: 400; } .elegant-icon-btn { background: rgba(255,255,255,0.6); border: 1px solid rgba(0,0,0,0.08); border-radius: 10px; color: #374151; padding: 7px 10px; font-size: 15px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; backdrop-filter: blur(10px); } .elegant-icon-btn:hover { background: rgba(255,255,255,0.9); border-color: rgba(0,0,0,0.15); box-shadow: 0 2px 8px rgba(0,0,0,0.06); } .elegant-body { flex: 1; overflow-y: auto; padding: 14px 20px 20px; display: flex; flex-direction: column; gap: 16px; } .elegant-card { background: rgba(255,255,255,0.7); border: 1px solid rgba(0,0,0,0.05); border-radius: 18px; padding: 16px; backdrop-filter: blur(10px); transition: box-shadow 0.2s; } .elegant-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.04); } .elegant-section-title { font-size: 12px; font-weight: 650; margin-bottom: 10px; display: flex; align-items: center; gap: 6px; color: #4B5563; text-transform: uppercase; letter-spacing: 0.5px; } .elegant-radio-group { display: flex; gap: 18px; flex-wrap: wrap; } .elegant-radio, .elegant-check { display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; color: #374151; transition: color 0.2s; } .elegant-radio:hover, .elegant-check:hover { color: #111827; } .elegant-radio input, .elegant-check input { accent-color: #4F46E5; } .elegant-divider { border-top: 1px solid rgba(0,0,0,0.06); margin: 12px 0; } .elegant-input, .elegant-textarea { width: 100%; padding: 10px 14px; background: rgba(255,255,255,0.8); border: 1px solid rgba(0,0,0,0.1); border-radius: 12px; color: #1f2937; font-size: 13px; outline: none; transition: all 0.2s; resize: vertical; } .elegant-input:focus, .elegant-textarea:focus { border-color: #6366F1; background: white; box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); } .elegant-select { width: 100%; padding: 10px 14px; background: rgba(255,255,255,0.8); border: 1px solid rgba(0,0,0,0.1); border-radius: 12px; font-size: 13px; outline: none; cursor: pointer; color: #1f2937; } .elegant-btn-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } .elegant-btn { border: none; border-radius: 14px; padding: 12px 4px; font-size: 13px; font-weight: 550; color: white; cursor: pointer; transition: all 0.2s ease; background: var(--btn-bg); box-shadow: 0 2px 8px rgba(0,0,0,0.08); display: flex; align-items: center; justify-content: center; gap: 4px; } .elegant-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); filter: brightness(1.05); } .elegant-btn:active { transform: translateY(0); box-shadow: 0 1px 4px rgba(0,0,0,0.1); } .elegant-btn:disabled { opacity: 0.5; cursor: not-allowed; filter: none; transform: none; } /* 日志区域:柔和卡片 */ .elegant-log { background: rgba(255,255,255,0.6); border-radius: 14px; padding: 12px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; color: #1f2937; max-height: 130px; overflow-y: auto; border: 1px solid rgba(0,0,0,0.05); } .elegant-log-entry { padding: 3px 8px; border-radius: 4px; margin: 2px 0; } .log-success { background: rgba(16, 185, 129, 0.12); border-left: 3px solid #10B981; } .log-warning { background: rgba(245, 158, 11, 0.12); border-left: 3px solid #F59E0B; } .log-error { background: rgba(239, 68, 68, 0.12); border-left: 3px solid #EF4444; } .log-info { background: rgba(59, 130, 246, 0.12); border-left: 3px solid #3B82F6; } @media (prefers-color-scheme: dark) { .elegant-log { background: rgba(0,0,0,0.3); color: #e5e7eb; } } `; document.head.appendChild(style); // 日志 const logArea = document.createElement('div'); logArea.className = 'elegant-log'; const addLog = (msg, type = 'info') => { const div = document.createElement('div'); div.className = `elegant-log-entry log-${type}`; div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; logArea.appendChild(div); logArea.scrollTop = logArea.scrollHeight; if (logArea.children.length > 150) logArea.firstChild.remove(); }; const log = { success: (m) => addLog(m, 'success'), warn: (m) => addLog(m, 'warning'), error: (m) => addLog(m, 'error'), info: (m) => addLog(m, 'info'), debug: (m) => { if (DEBUG_MODE) addLog(m, 'debug'); } }; // 核心功能函数 const getRandomVary = () => $1('#randomVary')?.checked ?? true; const getAutoAction = () => { const sel = $1('input[name="autoAction"]:checked'); return sel ? sel.value : 'save'; }; const getAutoSwitch = () => $1('#autoSwitchCourse')?.checked ?? false; const getAutoNext = () => $1('#autoNextAfterEval')?.checked ?? false; const getBatchLevel = () => { const sel = $1('#batchEvalLevel'); if (!sel) return { index: 0, score: '100', name: '优秀' }; const opt = sel.options[sel.selectedIndex]; return { index: parseInt(sel.value), score: opt.dataset.score, name: ['优秀','良好','合格','不合格'][sel.value] }; }; const getTeacherComment = () => $1('#evalTeacher')?.value.trim() || ''; const getBookComment = () => $1('#evalBook')?.value.trim() || ''; const dismissAlert = () => { const modal = $1('#alertModal'); if (modal && modal.style.display !== 'none') { modal.removeAttribute('aria-hidden'); $1('#btn_ok', modal)?.click(); $1('.bootbox-close', modal)?.click(); } }; setInterval(dismissAlert, 2000); const findBtn = (text, ids = []) => { const $btns = $('button:visible, input[type="button"]:visible, a.btn:visible'); const match = $btns.filter(function() { const t = ($(this).text() || $(this).val() || '').trim(); return ids.includes(this.id) || ids.some(id => $(this).hasClass(id)) || t === text || t.includes(text); }).first(); return match.length ? match[0] : null; }; const autoSave = () => { dismissAlert(); const btn = findBtn('保存', ['btn_bc', 'btn_xspj_bc']); if (btn) { btn.dispatchEvent(new MouseEvent('click', { bubbles: true })); log.success('✅ 已保存'); setTimeout(dismissAlert, 500); return true; } log.error('❌ 未找到保存按钮'); return false; }; const autoSubmit = () => { if (autoSave()) { setTimeout(() => { dismissAlert(); const btn = findBtn('提交', ['btn_tj', 'btn_xspj_tj']); if (btn) { btn.dispatchEvent(new MouseEvent('click', { bubbles: true })); log.success('✅ 已提交'); setTimeout(dismissAlert, 500); } else log.error('❌ 未找到提交按钮'); }, 1000); } }; const fillComments = () => { const teacher = getTeacherComment(), book = getBookComment(); const $panel = $('#panel_content'); const $inputs = $panel.length ? $panel.find('textarea:visible, input[type="text"]:visible') : $('textarea:visible, input[type="text"]:visible'); const $valid = $inputs.filter(function() { return !this.readOnly && !this.disabled && this.type !== 'hidden'; }); if ($valid.length > 0 && teacher) { $valid.eq(0).val(teacher).trigger('input').trigger('change'); log.success('✍️ 教师评语已填'); } if ($valid.length > 1 && book) { $valid.eq(1).val(book).trigger('input').trigger('change'); log.success('📘 教材评语已填'); } else if ($valid.length === 1 && book) { const cur = $valid.eq(0).val() || ''; $valid.eq(0).val(cur + (cur ? ';' : '') + book).trigger('input').trigger('change'); log.success('📘 教材评语合并'); } }; const selectAllItems = (radios, optionIndex) => { const groups = Math.ceil(radios.length / 4); for (let i = optionIndex; i < radios.length; i += 4) if (radios[i]) radios[i].checked = true; if (getRandomVary() && groups >= 2) { const changeIdx = Math.floor(Math.random() * groups); const start = changeIdx * 4; if (radios[start + optionIndex]) radios[start + optionIndex].checked = false; let newIdx = optionIndex === 0 ? 1 : optionIndex === 3 ? 2 : optionIndex + (Math.random() < 0.5 ? -1 : 1); if (radios[start + newIdx]) radios[start + newIdx].checked = true; log.success(`🎲 第${changeIdx+1}项改为${['优秀','良好','合格','不合格'][newIdx]}`); } log.success(`已选择${groups}项评价`); }; const checkSubmitted = () => { if ($1('#tjzt')?.value === '1') return true; if (!document.getElementsByClassName('radio-pjf').length) return true; const rows = $a('.tr-xspj td:nth-child(2)'); if (rows.length && !rows[0].querySelector('input,select,textarea') && rows[0].textContent.trim()) return true; return false; }; const switchCourse = (dir) => { const grid = $('#tempGrid'); if (!grid.length || !grid.jqGrid) { log.error('未找到课程表格'); return false; } const ids = grid.jqGrid('getDataIDs'); const cur = grid.jqGrid('getGridParam', 'selrow'); let idx = ids.indexOf(cur); if (idx < 0) idx = 0; const step = dir === 'next' ? 1 : -1; for (let i = idx + step; i >= 0 && i < ids.length; i += step) { const rd = grid.jqGrid('getRowData', ids[i]); if (getAutoSwitch() && rd.tjztmc === '提交') continue; grid.jqGrid('setSelection', ids[i]); setTimeout(() => $(`#${ids[i]}`).click(), 300); log.success(`切换到: ${rd.jxbmc || '课程'}`); return true; } log.warn('未找到可切换课程'); return false; }; const handleEval = async (idx, score, name) => { if (checkSubmitted()) { log.error('已提交,无法操作'); return; } await sleep(200); const radios = document.getElementsByClassName('radio-pjf'); if (!radios.length) { log.error('无评价选项'); return; } const inputs = document.getElementsByClassName('input-sm input-pjf'); if (inputs.length) inputs[0].value = score; selectAllItems(radios, idx); fillComments(); await sleep(800); const action = getAutoAction(); if (action === 'save') autoSave(); else if (action === 'submit') autoSubmit(); setTimeout(dismissAlert, 600); if (getAutoNext()) setTimeout(() => switchCourse('next'), 2000); }; const batchEval = async () => { const grid = $('#tempGrid'); if (!grid.length || !grid.jqGrid) { log.error('无课程列表,请等待页面完全加载'); return; } const ids = grid.jqGrid('getDataIDs'); const unsub = ids.filter(id => grid.jqGrid('getRowData', id).tjztmc !== '提交'); if (!unsub.length) { log.success('🎉 全部已提交'); return; } const level = getBatchLevel(); log.info(`🚀 批量评价 ${unsub.length} 门课,等级:${level.name}`); for (let i = 0; i < unsub.length; i++) { const id = unsub[i]; const rd = grid.jqGrid('getRowData', id); log.info(`[${i+1}/${unsub.length}] ${rd.jxbmc}`); grid.jqGrid('setSelection', id); await sleep(500); $(`#${id}`).click(); await sleep(2500); if (checkSubmitted()) continue; const radios = document.getElementsByClassName('radio-pjf'); if (!radios.length) continue; const inputs = document.getElementsByClassName('input-sm input-pjf'); if (inputs.length) inputs[0].value = level.score; selectAllItems(radios, level.index); fillComments(); const action = getAutoAction(); if (action === 'save') { autoSave(); await sleep(1500); } else if (action === 'submit') { autoSubmit(); await sleep(2500); } else { autoSave(); await sleep(1500); } dismissAlert(); } log.success('🏁 批量评价完成'); setTimeout(() => location.reload(), 2000); }; const panel = document.createElement('div'); panel.className = 'elegant-panel'; const header = document.createElement('div'); header.className = 'elegant-header'; header.innerHTML = `