// ==UserScript== // @name DGUT优学院作业互评显示评分人名字 // @namespace https://github.com/vanilla1108 // @version 0.1.0 // @description 让互评不再匿名——营造良好互评环境 // @author ZM25XC、itsdapi、yggk、vanilla // @match https://lms.dgut.edu.cn/homework/* // @icon https://lms.dgut.edu.cn/favicon.ico // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const API = 'https://lms.dgut.edu.cn'; const TAG = '[互评脚本]'; const log = (...a) => console.log(TAG, ...a); // ── 工具 ────────────────────────────────── const sleep = ms => new Promise(r => setTimeout(r, ms)); async function getJSON(url, token) { const res = await fetch(url, { headers: { AUTHORIZATION: token } }); return res.json(); } const STYLE = { reviewer: { display:'inline-block', color:'#8b7355', fontSize:'13px', fontWeight:'400', margin:'6px 0', padding:'4px 8px', borderLeft:'2px solid #c4a882', background:'rgba(196,168,130,0.06)' }, target: { display:'inline-block', color:'#6b8e8e', fontSize:'13px', fontWeight:'400', margin:'6px 0', padding:'4px 8px', borderLeft:'2px solid #8fbcbb', background:'rgba(143,188,187,0.06)' }, }; function makeLabel(text, preset) { const el = document.createElement('div'); const s = STYLE[preset] || STYLE.reviewer; el.style.cssText = Object.entries(s).map(([k,v]) => k.replace(/([A-Z])/g,'-$1').toLowerCase()+': '+v).join('; '); el.textContent = text; return el; } // ── 参数 ────────────────────────────────── function parseParams() { const m = location.hash.match(/stuDetail\/(\d+)\/(\d+)/); if (!m) return null; const studentid = m[1]; const homeworkid = m[2]; const classid = new URLSearchParams( (location.hash.split('?')[1] || '') ).get('ocId') || ''; const token = (document.cookie.match(/(?:^|;\s*)token=([^;]+)/) || [])[1] || ''; if (!token) return null; return { studentid, homeworkid, classid, token }; } // ── API ──────────────────────────────────── async function fetchUserInfo(uid, homeworkid, classid, token) { const [ur, cr] = await Promise.allSettled([ getJSON(`${API}/homeworkapi/homework/historyStudentHomework/${uid}/${uid}/${homeworkid}`, token), getJSON(`${API}/courseapi/classes?ocId=${classid}&pn=1&ps=9999&userId=${uid}&keyword=&lang=zh`, token), ]); const user = { name: '?', studentid: '?', className: '' }; if (ur.status === 'fulfilled' && ur.value.result?.user) { user.name = ur.value.result.user.name || '?'; user.studentid = ur.value.result.user.studentid || '?'; } if (cr.status === 'fulfilled' && cr.value.list?.length) { user.className = cr.value.list[0].className || ''; } return user; } // ── 渲染 ────────────────────────────────── function render(infoMap) { // 评价人(已给作业打分的)— 暖灰褐色 document.querySelectorAll('.peermain:not([data-peerdone])').forEach(el => { const uid = el.dataset.peeruid; const u = uid && infoMap[uid] ? infoMap[uid] : null; if (u) { insertLabel(el, `评价人:${u.name} | 学号:${u.studentid} | 班级:${u.className}`, 'reviewer'); } el.dataset.peerdone = '1'; }); // 待评人员(互评任务)— 灰青色 document.querySelectorAll('.peer_host:not([data-peerdone])').forEach(el => { const uid = el.dataset.peeruid; const u = uid && infoMap[uid] ? infoMap[uid] : null; if (u) { insertLabel(el, `待评价人员:${u.name} | 学号:${u.studentid} | 班级:${u.className}`, 'target'); } el.dataset.peerdone = '1'; }); } function insertLabel(after, text, style) { const el = makeLabel(text, style); if (after.parentNode) { after.parentNode.insertBefore(el, after.nextSibling); } } // ── 主流程 ───────────────────────────────── async function main() { log('开始'); const p = parseParams(); if (!p) { log('参数解析失败,退出'); return; } // 并行获取两路数据 const [dr, pr] = await Promise.allSettled([ getJSON(`${API}/homeworkapi/stuHomework/homeworkDetail/${p.homeworkid}/${p.studentid}/${p.classid}`, p.token), getJSON(`${API}/homeworkapi/stuHomework/peerReviewHomeworkDatil/${p.homeworkid}/${p.studentid}`, p.token), ]); const hwList = (dr.status === 'fulfilled' && dr.value.result?.peerReviewHomeworkList) || []; const peerList = (pr.status === 'fulfilled' && pr.value.result) || []; log(`评价者 ${hwList.length} 人,待评价 ${peerList.length} 人`); // 去重 const uidSet = new Set(); [...hwList, ...peerList].forEach(it => { if (it.userID) uidSet.add(it.userID); }); const uidList = Array.from(uidSet); // 并行获取所有用户信息 const results = await Promise.allSettled( uidList.map(uid => fetchUserInfo(uid, p.homeworkid, p.classid, p.token)) ); const infoMap = {}; uidList.forEach((uid, i) => { infoMap[uid] = results[i].status === 'fulfilled' ? results[i].value : { name: '?', studentid: '?', className: '' }; }); log('用户:', Object.values(infoMap).map(u => u.name).join(', ')); // 把 uid 写到 DOM 元素上(以便 render 时按 uid 匹配,而非按索引匹配) await sleep(500); // 等 Vue 渲染 document.querySelectorAll('.peermain').forEach((el, i) => { if (hwList[i]) el.dataset.peeruid = hwList[i].userID; }); document.querySelectorAll('.peer_host').forEach((el, i) => { if (peerList[i]) el.dataset.peeruid = peerList[i].userID; }); // 首屏渲染 render(infoMap); // 持续监听(Tab 切换后 Vue 会重建 DOM) const observer = new MutationObserver(() => { const hasNew = document.querySelector('.peermain:not([data-peerdone]), .peer_host:not([data-peerdone])'); if (hasNew) { // 重新关联 uid(Vue 重建后 dataset 丢失) document.querySelectorAll('.peermain:not([data-peeruid])').forEach((el, i) => { if (hwList[i]) el.dataset.peeruid = hwList[i].userID; }); document.querySelectorAll('.peer_host:not([data-peeruid])').forEach((el, i) => { if (peerList[i]) el.dataset.peeruid = peerList[i].userID; }); render(infoMap); } }); observer.observe(document.body, { childList: true, subtree: true }); // 30 秒后停掉监听(省资源) setTimeout(() => observer.disconnect(), 30000); log('完成'); } main().catch(e => console.error(TAG, e)); })();