// ==UserScript==
// @name 中南林一键评教
// @namespace http://tampermonkey.net/
// @version 1.3.2
// @description 自动完成教学质量平台的学生评教(满分策略,随机一题减1分)
// @author spider-go
// @match *://*.webvpn.csuft.edu.cn/*
// @match *://jxzlpt.csuft.edu.cn/*
// @icon https://www.csuft.edu.cn/favicon.ico
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
var host = window.location.hostname;
if (host.indexOf('jxzlpt') === -1) return;
console.log('[一键评教] 已加载');
// ========== 评语库 ==========
var COMMENTS = [
'老师授课认真负责,教学效果好',
'老师教学内容丰富,讲解清晰透彻',
'老师备课充分,课堂互动良好',
'老师治学严谨,对学生要求严格但耐心',
'老师上课生动有趣,能调动学生积极性',
'老师知识渊博,能理论联系实际',
'老师讲课重点突出,逻辑性强',
'老师认真负责,关心学生学习情况',
'老师教学方法灵活,课堂气氛活跃',
'老师讲解深入浅出,易于理解',
];
var useRandom = localStorage.getItem('spider_eval_random') !== '0';
// ========== 样式 ==========
var style = document.createElement('style');
style.textContent = [
'.spider-eval-btn{position:fixed!important;z-index:99999!important;padding:clamp(8px,2vw,16px) clamp(16px,4vw,32px)!important;',
'background:linear-gradient(135deg,#667eea,#764ba2)!important;color:#fff!important;',
'border:none!important;border-radius:clamp(25px,5vw,50px)!important;font-size:clamp(12px,2.5vw,18px)!important;',
'font-weight:600!important;cursor:pointer!important;',
'box-shadow:0 4px 15px rgba(102,126,234,0.4)!important;',
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif!important;}',
'.spider-eval-btn:hover{box-shadow:0 6px 20px rgba(102,126,234,0.6)!important;}',
'.spider-eval-btn:disabled{opacity:.6!important;cursor:not-allowed!important;}',
'.spider-eval-box{position:fixed!important;z-index:99999!important;',
'display:flex!important;flex-wrap:wrap!important;align-items:center!important;justify-content:center!important;gap:6px!important;',
'max-width:calc(100vw - 30px)!important;',
'background:rgba(248,249,252,.92)!important;backdrop-filter:blur(6px)!important;',
'border-radius:16px!important;padding:clamp(4px,1.5vw,8px) clamp(6px,2vw,12px)!important;',
'box-shadow:0 4px 20px rgba(0,0,0,.1)!important;',
'border:1px solid rgba(255,255,255,.6)!important;',
'cursor:grab!important;user-select:none!important;',
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif!important;}',
'.spider-eval-box.dragging{cursor:grabbing!important;opacity:.85!important;}',
'.spider-eval-box .spider-eval-btn{position:relative!important;z-index:auto!important;cursor:pointer!important;}',
'.spider-eval-box .spider-eval-tg{position:relative!important;z-index:auto!important;bottom:auto!important;right:auto!important;}',
'.spider-eval-tg{position:fixed!important;z-index:99999!important;display:flex!important;',
'align-items:center!important;gap:6px!important;font-size:12px!important;color:#666!important;',
'cursor:pointer!important;user-select:none!important;background:rgba(255,255,255,.95)!important;',
'padding:4px 10px!important;border-radius:20px!important;',
'box-shadow:0 2px 8px rgba(0,0,0,.1)!important;font-family:inherit!important;}',
'.spider-eval-tg .sl{width:32px!important;height:18px!important;background:#ccc!important;',
'border-radius:9px!important;position:relative!important;transition:background .3s!important;flex-shrink:0!important;}',
'.spider-eval-tg .sl::after{content:""!important;width:14px!important;height:14px!important;',
'background:#fff!important;border-radius:50%!important;position:absolute!important;',
'top:2px!important;left:2px!important;transition:transform .3s!important;}',
'.spider-eval-tg .sl.on{background:#667eea!important;}',
'.spider-eval-tg .sl.on::after{transform:translateX(14px)!important;}',
'.spider-eval-lg{position:fixed!important;z-index:99998!important;width:min(400px,calc(100vw - 30px))!important;',
'max-height:250px!important;overflow-y:auto!important;background:rgba(0,0,0,.85)!important;',
'color:#fff!important;border-radius:12px!important;padding:10px 14px!important;',
'font-size:13px!important;font-family:Consolas,monospace!important;line-height:1.6!important;',
'display:none!important;box-shadow:0 4px 20px rgba(0,0,0,.3)!important;}',
'.spider-eval-lg .ok{color:#52c41a;}.spider-eval-lg .er{color:#ff4d4f;}',
'.spider-eval-lg .in{color:#1890ff;}.spider-eval-lg .wa{color:#faad14;}',
].join('');
document.head.appendChild(style);
var BASE = window.location.origin;
var btn, lg;
function log(msg, t) {
if (!lg) return;
var d = document.createElement('div');
d.className = t;
d.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg;
lg.appendChild(d);
lg.scrollTop = lg.scrollHeight;
}
function gt() {
var m = document.cookie.match(/(?:^|;\s*)token=([^;]*)/);
return m ? m[1] : null;
}
async function ap(p, b) {
var h = { 'Accept': 'application/json, text/plain, */*' };
var t = gt();
if (t) h['Authorization'] = 'Bearer' + t;
if (b && typeof b === 'string') h['Content-Type'] = 'application/json;charset=UTF-8';
var r = await fetch(BASE + p, { method: 'POST', headers: h, credentials: 'include', body: b });
return await r.json();
}
async function getTasks() {
var d = await ap('/api/xspj/xspj/getXspjtask');
if (d.code === 200 && d.data && d.data.pageData) return d.data.pageData;
throw new Error((d && d.message) || '获取任务失败');
}
async function getCourses(id) {
var d = await ap('/api/xspj/xspj/getXspjStudentCourses?taskid=' + id);
if (d.code === 200 && d.data && d.data.pageData) return d.data.pageData;
throw new Error((d && d.message) || '获取课程失败');
}
async function getQuestions(iid, ct) {
var d = await ap('/api/xspj/xspj/getXspjTindexSystem?indexid=' + iid + '&pjcoursetype=' + encodeURIComponent(ct));
if (d.code === 200 && d.data && d.data.pageData) return d.data.pageData;
throw new Error((d && d.message) || '获取题目失败');
}
async function sub(e) {
var d = await ap('/api/xspj/xspj/saveStudentComment', JSON.stringify(e));
if (d.code === 200) return true;
throw new Error((d && d.message) || '提交失败');
}
function gen(qs) {
var ans = [], ts = 0, si = [];
qs.forEach(function(q, i) {
var a = { index_order: q.ordor, sfbt: q.isemptyed || '否', yjzb: q.firstlevlindex, index_type: q.type, indexid: q.indexid };
if (q.type === '打分题' && q.isscored === '是') {
var s = Math.round(q.score || 10);
a.index_score = String(s); a.index_title = String(s);
ts += s; si.push(i);
} else if (q.type === '问答题') {
a.index_score = '0';
a.index_title = (q.isemptyed === '否' && useRandom) ? COMMENTS[Math.floor(Math.random() * COMMENTS.length)] : '';
}
ans.push(a);
});
if (si.length > 0) {
var idx = si[Math.floor(Math.random() * si.length)];
var d = Math.max(0, Math.round(qs[idx].score) - 1);
ans[idx].index_score = String(d); ans[idx].index_title = String(d);
ts = ts - Math.round(qs[idx].score) + d;
}
return { answers: ans, totalScore: ts };
}
async function start() {
if (!btn || !lg) return;
btn.disabled = true;
btn.textContent = '⏳ 评教中...';
lg.innerHTML = '';
lg.style.display = 'block';
try {
log('获取评教任务...', 'in');
var tasks = await getTasks();
log('找到 ' + tasks.length + ' 个任务', 'in');
var ok = 0, fail = 0, skip = 0;
for (var t = 0; t < tasks.length; t++) {
var task = tasks[t];
if (task.currentStatus !== '进行中') { log('任务已结束,跳过', 'wa'); continue; }
log('任务:' + task.taskname, 'in');
var courses = await getCourses(task.taskid);
log('该任务 ' + courses.length + ' 门课', 'in');
for (var c = 0; c < courses.length; c++) {
var co = courses[c];
var key = co.coursename + ' - ' + co.teachername;
if (co.hassubmit === 1) { log('⏭️ 已评:' + key, 'wa'); skip++; continue; }
log('评教:' + key, 'in');
try {
var qs = await getQuestions(task.indexid, co.pjcoursetype);
if (!qs || qs.length === 0) { log('❌ ' + key + ' 未开放', 'er'); fail++; continue; }
var r = gen(qs);
var now = new Date();
var ct = now.getFullYear() + '-' +
String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0') + ' ' +
String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0') + ':' +
String(now.getSeconds()).padStart(2,'0');
await sub([{
taskid: task.taskid, classno: co.classno, coursecode: co.coursecode,
coursename: co.coursename, jobnumber: co.jobnumber, studentid: co.studentid,
studentname: co.studentname, teachername: co.teachername, yearterm: co.yearterm,
totalscore: r.totalScore, pjcoursetype: co.pjcoursetype,
courseorgcode: co.courseorgcode, courseorgname: co.courseorgname,
evaluateResult: r.answers, commit_time: ct,
}]);
log('✅ ' + key + '(' + r.totalScore + '分)', 'ok');
ok++;
await new Promise(function(r2) { setTimeout(r2, 500); });
} catch (e) {
log('❌ ' + key + ':' + e.message, 'er');
fail++;
}
}
}
log('==============', 'in');
if (ok === 0 && fail === 0 && skip > 0) {
log('全部已评教完毕 ✅', 'ok');
btn.textContent = '✅ 全部已评';
} else {
log('成功 ' + ok + ',失败 ' + fail + ',跳过 ' + skip, ok > 0 ? 'ok' : 'wa');
btn.textContent = '✅ 完成';
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (e) {
log('❌ 错误:' + e.message, 'er');
btn.textContent = '🤖 一键评教';
}
btn.disabled = false;
}
// ========== 拖动 ==========
function makeDraggable(el) {
var x0, y0, l0, t0, dragging = false;
function isInteractive(t) {
while (t && t !== el) {
if (t.tagName === 'BUTTON' || t.tagName === 'INPUT' || t.tagName === 'LABEL') return true;
t = t.parentNode;
}
return false;
}
function onStart(e) {
if (btn.disabled) return;
if (isInteractive(e.target)) return;
var ev = e.touches ? e.touches[0] : e;
x0 = ev.clientX; y0 = ev.clientY;
l0 = el.offsetLeft; t0 = el.offsetTop;
el.classList.add('dragging');
dragging = true;
e.preventDefault();
}
function onMove(e) {
if (!dragging) return;
var ev = e.touches ? e.touches[0] : e;
el.style.left = (l0 + ev.clientX - x0) + 'px';
el.style.top = (t0 + ev.clientY - y0) + 'px';
el.style.right = 'auto'; el.style.bottom = 'auto';
e.preventDefault();
}
function onEnd() {
if (!dragging) return;
dragging = false;
el.classList.remove('dragging');
}
el.addEventListener('mousedown', onStart);
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onEnd);
el.addEventListener('touchstart', onStart, { passive: false });
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', onEnd);
}
// ========== 注入 ==========
function inject() {
if (document.getElementById('spider-box')) return;
var box = document.createElement('div');
box.id = 'spider-box';
box.className = 'spider-eval-box';
box.style.bottom = '20px'; box.style.right = '20px';
btn = document.createElement('button');
btn.id = 'spider-btn';
btn.className = 'spider-eval-btn';
btn.textContent = '🤖 一键评教';
btn.onclick = start;
box.appendChild(btn);
var tg = document.createElement('div');
tg.className = 'spider-eval-tg';
tg.innerHTML = '随机评语';
tg.addEventListener('click', function(e) {
var sl = tg.querySelector('.sl');
sl.classList.toggle('on');
useRandom = sl.classList.contains('on');
localStorage.setItem('spider_eval_random', useRandom ? '1' : '0');
});
tg.addEventListener('touchstart', function(e) { e.stopPropagation(); });
tg.addEventListener('mousedown', function(e) { e.stopPropagation(); });
box.appendChild(tg);
document.body.appendChild(box);
makeDraggable(box);
lg = document.createElement('div');
lg.id = 'spider-lg';
lg.className = 'spider-eval-lg';
lg.style.bottom = 'calc(20px + 66px)'; lg.style.right = '20px';
lg.onclick = function() { lg.style.display = lg.style.display === 'none' ? 'block' : 'none'; };
document.body.appendChild(lg);
log('🟢 就绪,点击按钮评教', 'in');
log('💡 可拖动容器到任意位置', 'in');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', inject);
} else {
inject();
}
var iv = setInterval(function() {
if (document.body && !document.getElementById('spider-box')) inject();
if (document.getElementById('spider-box')) clearInterval(iv);
}, 2000);
setTimeout(function() { clearInterval(iv); }, 10000);
})();