某教务系统快捷评教脚本
本文默认你已对浏览器 F12 工具、书签以及 Tampermonkey 有基本概念。如有疑问,可在评论区留言。
需求背景
每到期末,教务系统都会要求为若干任课教师填写满意度问卷。
系统常见限制:- 每位教师所有指标不能完全一致——必须有 1 项与众不同。
- 评分控件为形如
html <select name="DataGrid1$ctl02$JS1">…</select>
ASP.NET 会把行号、列号揉进name / id
。 - 列数(教师数)可能 ≥ 1,并且列号既有
JS1
、也可能写成小写js1
。
目标
对每位教师:只留 1 个指标为
A
(或任意你想要的 diffVal),其余全为B
(defaultVal);
绝不多选,也绝不少选。
0. 快速上手
javascript:(()=>{const d="B",a="A";const w=(s,v)=>{if(s.value!=v){s.value=v;["change","input","blur"].forEach(e=>s.dispatchEvent(new Event(e,{bubbles:!0})))}};const g=s=>((s.name.match(/\$[Jj][Ss]\d+/)||s.id.match(/DataGrid1_[Jj][Ss]\d+/)||[])[0]||"").toUpperCase();const f=t=>{const S=[...t.querySelectorAll('select[id^="DataGrid1_"],select[name^="DataGrid1$"]')];if(!S.length)return!1;const M={};S.forEach(s=>{const k=g(s);k&&(M[k]??=[]).push(s)});Object.values(M).forEach(col=>{col.forEach(x=>w(x,d));w(col[Math.random()*col.length|0],a);col.filter(x=>x.value===a).slice(1).forEach(x=>w(x,d))});return!0};const all=t=>{let ok=f(t);t.querySelectorAll("iframe").forEach(fr=>{try{fr.contentDocument&&(ok=all(fr.contentDocument)||ok)}catch{}});return ok};all(document)})();void 0;
- 把整行保存为书签(Bookmarklet)。
- 打开评教页面 → 点书签 → 三秒内全部填完。
下文将详细拆解脚本逻辑,并给出控制台一次性运行和 Tampermonkey 自动化两种替代用法。
1. 核心原理
步骤 | 关键点 | 说明 |
---|---|---|
元素定位 | querySelectorAll('select[id^="DataGrid1_"], select[name^="DataGrid1$"]') | ASP.NET 会双份写入 id 和 name ;二者择一即可覆盖绝大多数学校系统。 |
教师分组 | 正则捕获 JS\d+ / js\d+ | 列式布局:同列同教师,只需识别列号即可。通过 toUpperCase() 解决大小写混写。 |
批量赋值 | defaultVal 全覆盖 → 随机抽 1 项改 diffVal | 先打相同等级,再打不同等级,保证整洁。 |
二次校验 | filter(sel => sel.value === diffVal) | 若用户手动改动导致一列出现多个 A ,脚本会自动把多余的改回默认分。 |
事件触发 | change + input + blur | 老式教务系统常监听其中一种;一次性全部 dispatch,确保「人工操作」痕迹。 |
iframe 递归 | document.querySelectorAll("iframe") | 部分系统把评分表嵌在子框架里;递归遍历可 100% 覆盖。 |
一句话:脚本就是——定位 → 分组 → 覆盖 → 打不同评分 → 校验评分 → 通知。
2. 三种使用方式
2.1 控制台临时脚本
- 进入评教页面,滚动到底部确保全部控件加载。
F12 → Console
,粘贴下方完整版脚本,回车。- 弹出
✅
后直接点击「提交」。
(()=>{
/* ========= 1. 自定义 ========= */
const defaultVal = "B"; // 其它评分
const diffVal = "A"; // 每位教师唯一不同评分
const maxRetry = 10; // 最多重试
const retryDelay = 400; // 间隔 ms
/* ============================ */
/* 给 <select> 赋值并触发事件 */
function setSelect(sel,val){
if(sel.value===val) return;
sel.value = val;
["change","input","blur"].forEach(e=>
sel.dispatchEvent(new Event(e,{bubbles:true}))
);
}
/* 提取列号:JS1 / js1 / JS10 … → 统一成大写 JS1、JS10 */
function getColKey(sel){
// 1) 先尝试 name="…$ctl02$JS3"
let m = sel.name.match(/\$([Jj][Ss]\d+)\b/);
if(m) return m[1].toUpperCase();
// 2) 再尝试 id="DataGrid1_JS3_0"
m = sel.id.match(/DataGrid1_([Jj][Ss]\d+)_/);
if(m) return m[1].toUpperCase();
return null; // 无法识别
}
/* 处理单个 document */
function fillOnce(doc){
const selects=[...doc.querySelectorAll(
'select[id^="DataGrid1_"], select[name^="DataGrid1$"]'
)];
if(!selects.length) return false;
const groups={}; // {JS1:[sel…], JS2:[sel…]}
selects.forEach(sel=>{
const key=getColKey(sel);
if(key) (groups[key] ||= []).push(sel);
});
// 若只识别出 0/1 列,提前报错方便排查
if(Object.keys(groups).length<=1){
console.warn("⚠️ 只识别到 1 列教师,请检查 getColKey 正则是否匹配。");
}
/* 每位教师:全部 defaultVal → 抽 1 个改 diffVal → 二次校验 */
Object.values(groups).forEach(col=>{
col.forEach(s=>setSelect(s,defaultVal)); // 1) 全填 B
const lucky = col[Math.random()*col.length|0]; // 2) 抽一个改 A
setSelect(lucky,diffVal);
const diffList = col.filter(s=>s.value===diffVal);// 3) 若意外多于 1 个
diffList.slice(1).forEach(s=>setSelect(s,defaultVal));
});
return true;
}
/* 递归遍历 iframe */
function fillAll(doc=document){
let ok=fillOnce(doc);
doc.querySelectorAll("iframe").forEach(f=>{
try{f.contentDocument&&(ok=fillAll(f.contentDocument)||ok);}catch{}
});
return ok;
}
/* 自动重试,确保元素加载完 */
let tries=0;
function runner(){
if(fillAll()){
const sta=[...document.querySelectorAll(
'select[id^="DataGrid1_"], select[name^="DataGrid1$"]'
)].reduce((m,s)=>(m[s.value]=(m[s.value]||0)+1,m),{});
console.table(sta); // 控制台输出 {A: 教师列数, B: 其它}
alert(`✅ 已完成:每位教师仅 1 个「${diffVal}」,其余全「${defaultVal}」。`);
}else if(++tries<maxRetry){
console.log(`第 ${tries} 次未找到控件,${retryDelay} ms 后重试…`);
setTimeout(runner,retryDelay);
}else{
alert("❌ 多次重试仍未定位到下拉框,请确认页面/iframe 已完全加载或发我 HTML 结构。");
}
}
document.readyState==="complete"
? runner()
: window.addEventListener("load",runner);
})();
2.2 Bookmarklet(书签)
- 浏览器书签栏新建一条,命名随意「一键评教」。
- 把 快速上手段落中的 整行 javascript: 贴到 URL。
- 以后进入评教页面 → 点书签 → 自动填完。
优势:跨浏览器、免扩展;劣势:仍需手点一下。
2.3 Tampermonkey / Violentmonkey 自动脚本
- 安装 Tampermonkey(Chrome / Edge)、Violentmonkey(Firefox)等。
- 新建脚本,将 完整版 粘进去;把
@match
改成自己学校评教网址。 - 保存。
- 以后访问该网页,脚本 自动 执行,无需任何操作。
// ==UserScript==
// @name 评教批量填写
// @match *://pj.yourschool.edu/* // ← 按实际地址修改
// @run-at document-end
// ==/UserScript==
// ……(粘贴完整版主体函数即可)
3. 自定义评价等级
需求 | 改法 |
---|---|
想给大部分 A,每列 1 个 C | defaultVal="A"; diffVal="C" |
指定第 2 行永远是 diffVal | 把 set(col[Math.random()*…], diffVal) 改成 set(col[1], diffVal) |
4. 总结
- 定位:
querySelectorAll
抓<select>
- 分组:用
JS\d+ / js\d+
列号区分教师 - 赋值:全填默认,再随机 1 个不同
- 保证唯一:注意二次校验去重
- 兼容性:大小写、iframe、事件触发
- 三种用法:一次性粘贴 / 书签 / Tampermonkey
有了这段脚本,期末评教再也不用逐个点选——一次点击,秒速搞定。
参考/鸣谢
- 教务系统普适命名规范 (
DataGrid1$ctlXX$JSX
) - MDN:
Element.dispatchEvent
用法 - StackOverflow:如何在 JS 内同时触发
change
&input
事件