某教务系统快捷评教脚本

2025 年 5 月 31 日 星期六(已编辑)
2
AI 生成的摘要
此内容由 AI 生成
本文介绍了一种自动化填写教务系统满意度问卷的方法。具体步骤包括元素定位、教师分组、批量赋值、二次校验、事件触发和iframe递归。用户可以通过控制台临时脚本、书签或Tampermonkey自动脚本三种方式来使用该脚本。同时,提供了自定义评价等级的方法,可以指定默认评分和不同评分。此外,文章还总结了脚本实现的核心原理和兼容性注意事项。

某教务系统快捷评教脚本

本文默认你已对浏览器 F12 工具、书签以及 Tampermonkey 有基本概念。如有疑问,可在评论区留言。


  • 需求背景
    每到期末,教务系统都会要求为若干任课教师填写满意度问卷。
    系统常见限制:

    1. 每位教师所有指标不能完全一致——必须有 1 项与众不同。
    2. 评分控件为形如
      html <select name="DataGrid1$ctl02$JS1">…</select>
      ASP.NET 会把行号、列号揉进 name / id
    3. 列数(教师数)可能 ≥ 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 会双份写入 idname;二者择一即可覆盖绝大多数学校系统。
教师分组 正则捕获 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 控制台临时脚本

  1. 进入评教页面,滚动到底部确保全部控件加载
  2. F12 → Console,粘贴下方完整版脚本,回车。
  3. 弹出 后直接点击「提交」。
(()=>{
/* ========= 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(书签)

  1. 浏览器书签栏新建一条,命名随意「一键评教」。
  2. 快速上手段落中的 整行 javascript: 贴到 URL。
  3. 以后进入评教页面 → 点书签 → 自动填完。

优势:跨浏览器、免扩展;劣势:仍需手点一下。


2.3 Tampermonkey / Violentmonkey 自动脚本

  1. 安装 Tampermonkey(Chrome / Edge)、Violentmonkey(Firefox)等。
  2. 新建脚本,将 完整版 粘进去;把 @match 改成自己学校评教网址。
  3. 保存。
  4. 以后访问该网页,脚本 自动 执行,无需任何操作。
// ==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)
  • MDNElement.dispatchEvent 用法
  • StackOverflow:如何在 JS 内同时触发 change & input 事件
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • 某教务系统快捷评教脚本 - 顾の博客