您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
检查学生在code.xdf.cn的作业完成情况并生成HTML表格 2025/3/4 23:22:37
// ==UserScript== // @license MIT // @name 新东方作业检查器 // @namespace Violentmonkey Scripts // @version 0.4 // @description 检查学生在code.xdf.cn的作业完成情况并生成HTML表格 2025/3/4 23:22:37 // @author 天津新东方编程杨敖儒GY // @match https://code.xdf.cn/oj/home // @grant GM_xmlhttpRequest // @grant unsafeWindow // ==/UserScript== (function() { 'use strict'; function createUI() { const div = document.createElement('div'); div.style.position = 'absolute'; div.style.top = '80px'; div.style.right = '10px'; div.style.width = '300px'; div.style.minHeight = '200px'; div.style.background = '#fff'; div.style.padding = '10px'; div.style.border = '1px solid #ccc'; div.style.zIndex = '1000'; div.style.cursor = 'move'; div.style.boxSizing = 'border-box'; div.innerHTML = ` <h3 style="margin: 0; padding-bottom: 5px;">检查作业工具 by:GY</h3> <input type="file" id="fileInput" accept=".txt" style="width: 100%;"><br><br> <label>请输入题号数量:</label> <input type="number" id="problemCount" min="1" style="width: 100%;"><br><br> <button id="confirmCount">确认</button> <div id="problemInputs"></div> <button id="startCheck" style="display:none;">开始检查</button> <p id="status"></p> `; document.body.appendChild(div); const fileInput = document.getElementById('fileInput'); const problemCountInput = document.getElementById('problemCount'); const confirmButton = document.getElementById('confirmCount'); const startButton = document.getElementById('startCheck'); makeDraggable(div); confirmButton.addEventListener('click', showProblemInputs); startButton.addEventListener('click', startChecking); fileInput.addEventListener('change', () => { console.log('文件已上传,检查 problemCount 输入框状态:', { disabled: problemCountInput.disabled, readonly: problemCountInput.readOnly, display: problemCountInput.style.display }); problemCountInput.focus(); }); } function makeDraggable(element) { let posX = 0, posY = 0, mouseX = 0, mouseY = 0; element.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); mouseX = e.clientX; mouseY = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); posX = mouseX - e.clientX; posY = mouseY - e.clientY; mouseX = e.clientX; mouseY = e.clientY; element.style.top = (element.offsetTop - posY) + 'px'; element.style.left = (element.offsetLeft - posX) + 'px'; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } function showProblemInputs() { const count = parseInt(document.getElementById('problemCount').value); if (!count || count <= 0) { alert('请输入有效的题号数量!'); return; } const problemInputsDiv = document.getElementById('problemInputs'); problemInputsDiv.innerHTML = ''; for (let i = 0; i < count; i++) { const input = document.createElement('input'); input.type = 'text'; input.placeholder = `请输入第${i+1}题题号(如P1021)`; input.id = `problem_${i}`; input.style.width = '100%'; input.style.marginBottom = '5px'; input.disabled = false; problemInputsDiv.appendChild(input); problemInputsDiv.appendChild(document.createElement('br')); } document.getElementById('startCheck').style.display = 'block'; const firstInput = document.getElementById('problem_0'); if (firstInput) { console.log('题号输入框生成后状态:', { disabled: firstInput.disabled, readonly: firstInput.readOnly, display: firstInput.style.display }); firstInput.focus(); } } // 开始检查 async function startChecking() { const fileInput = document.getElementById('fileInput'); const problemCount = parseInt(document.getElementById('problemCount').value); const status = document.getElementById('status'); if (!fileInput.files.length) { alert('请先选择包含学生URL的txt文件!'); return; } const problemIds = []; for (let i = 0; i < problemCount; i++) { const pid = document.getElementById(`problem_${i}`).value.trim(); if (!pid) { alert(`第${i+1}题题号不能为空!`); return; } problemIds.push(pid); } status.textContent = '正在读取文件...'; const students = await readFile(fileInput.files[0]); if (!students.length) { alert('文件内容为空或格式错误!'); return; } status.textContent = '正在检查作业完成情况...'; const results = await checkProblems(students, problemIds); generateHTML(students, problemIds, results, fileInput.files[0].name); status.textContent = '检查完成,结果已生成!'; } // 读取txt文件 function readFile(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { const lines = e.target.result.split('\n').filter(line => line.trim()); const students = lines.map(line => { const [name, url] = line.trim().split(/\s+/); return { name, url }; }); resolve(students); }; reader.readAsText(file); }); } async function checkProblems(students, problemIds) { const results = {}; for (const student of students) { results[student.name] = {}; const completedProblems = await getCompletedProblems(student.url); for (const pid of problemIds) { results[student.name][pid] = completedProblems.includes(pid) ? '完成' : '未完成'; } await new Promise(resolve => setTimeout(resolve, 1000)); } return results; } function getCompletedProblems(url) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: url, onload: async function(response) { const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, 'text/html'); const completedProblems = await waitForDynamicContent(doc, url); resolve(completedProblems); }, onerror: function() { console.error(`无法加载页面: ${url}`); resolve([]); } }); }); } function waitForDynamicContent(doc, url) { return new Promise((resolve) => { const problemSpans = doc.querySelectorAll('.problem-btn button span'); if (problemSpans.length > 0) { const completedProblems = Array.from(problemSpans).map(span => span.textContent.trim()); resolve(completedProblems); return; } const win = window.open(url, '_blank'); if (!win) { console.error('请允许弹出窗口以检查动态内容'); resolve([]); return; } win.addEventListener('load', () => { const checkInterval = setInterval(() => { const spans = win.document.querySelectorAll('.problem-btn button span'); if (spans.length > 0) { const completedProblems = Array.from(spans).map(span => span.textContent.trim()); clearInterval(checkInterval); win.close(); resolve(completedProblems); } }, 500); setTimeout(() => { clearInterval(checkInterval); win.close(); resolve([]); }, 10000); }); }); } function generateHTML(students, problemIds, results, fileName) { const date = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); let htmlContent = ` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${date} 查题情况</title> <style> table { border-collapse: collapse; margin: 20px auto; } th, td { border: 1px solid black; padding: 8px; text-align: center; } th { background-color: #f2f2f2; } .title { text-align: center; font-size: 1.2em; margin: 10px 0; } </style> </head> <body> <div class="title">${date} 查题情况</div> <table> <tr><th>姓名</th>${problemIds.map(pid => `<th>${pid}</th>`).join('')}</tr> `; for (const student of students) { htmlContent += '<tr>'; htmlContent += `<td>${student.name}</td>`; for (const pid of problemIds) { htmlContent += `<td>${results[student.name][pid]}</td>`; } htmlContent += '</tr>'; } htmlContent += ` </table> </body> </html> `; const blob = new Blob([htmlContent], { type: 'text/html' }); const link = document.createElement('a'); const dir = fileName.substring(0, fileName.lastIndexOf('/')) || ''; link.href = URL.createObjectURL(blob); link.download = `${dir}/作业检查结果_${Date.now()}.html`; link.click(); } createUI(); })();