别着急,坐和放宽
本文默认你已对浏览器 F12 工具、书签以及 Tampermonkey 有基本概念。如有疑问,可在评论区留言。
需求背景
每到期末,教务系统都会要求为若干任课教师填写满意度问卷。
系统常见限制:
html
<select name="DataGrid1$ctl02$JS1">…</select>
name / id
。JS1
、也可能写成小写 js1
。目标
对每位教师:只留 1 个指标为
A
(或任意你想要的 diffVal),其余全为B
(defaultVal);
绝不多选,也绝不少选。
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;
下文将详细拆解脚本逻辑,并给出控制台一次性运行和 Tampermonkey 自动化两种替代用法。
步骤 | 关键点 | 说明 |
---|---|---|
元素定位 | 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% 覆盖。 |
一句话:脚本就是——定位 → 分组 → 覆盖 → 打不同评分 → 校验评分 → 通知。
F12 → Console
,粘贴下方完整版脚本,回车。✅
后直接点击「提交」。优势:跨浏览器、免扩展;劣势:仍需手点一下。
@match
改成自己学校评教网址。需求 | 改法 |
---|---|
想给大部分 A,每列 1 个 C | defaultVal="A"; diffVal="C" |
指定第 2 行永远是 diffVal | 把 set(col[Math.random()*…], diffVal) 改成 set(col[1], diffVal) |
querySelectorAll
抓 <select>
JS\d+ / js\d+
列号区分教师有了这段脚本,期末评教再也不用逐个点选——一次点击,秒速搞定。
DataGrid1$ctlXX$JSX
)Element.dispatchEvent
用法change
& input
事件(()=>{
/* ========= 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);
})();
// ==UserScript==
// @name 评教批量填写
// @match *://pj.yourschool.edu/* // ← 按实际地址修改
// @run-at document-end
// ==/UserScript==
// ……(粘贴完整版主体函数即可)