在页面上显示各课程的作业和考试分数,并按 activity_id 升序排列,仅显示特定 API 中的作业和考试
// ==UserScript==
// @name Display Homework and Exam Scores for Courses (Filtered & Sorted)
// @namespace http://tampermonkey.net/
// @version 1.7
// @description 在页面上显示各课程的作业和考试分数,并按 activity_id 升序排列,仅显示特定 API 中的作业和考试
// @author Cold_Ink
// @match *://courses.zju.edu.cn/course/*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 从 URL 中提取课程 ID
const courseUrlMatch = window.location.pathname.match(/\/course\/(\d+)\//);
if (!courseUrlMatch) {
console.error('无法从 URL 中找到课程 ID。');
return;
}
const courseId = courseUrlMatch[1];
// 创建一个容器来显示分数
const scoreContainer = document.createElement('div');
scoreContainer.id = 'score-container';
scoreContainer.style.position = 'fixed';
scoreContainer.style.bottom = '10px';
scoreContainer.style.right = '10px';
scoreContainer.style.zIndex = '1000';
scoreContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
scoreContainer.style.border = '1px solid #ccc';
scoreContainer.style.borderRadius = '5px';
scoreContainer.style.boxShadow = '0 0 5px rgba(0,0,0,0.1)';
scoreContainer.style.maxHeight = '400px';
scoreContainer.style.overflowY = 'auto';
scoreContainer.style.padding = '10px';
scoreContainer.style.width = '300px';
scoreContainer.style.fontFamily = 'Arial, sans-serif';
scoreContainer.style.fontSize = '12px';
scoreContainer.style.lineHeight = '1.4';
scoreContainer.style.color = '#333';
scoreContainer.innerHTML = '<h4 style="margin: 0 0 10px 0; font-size: 14px; text-align: center;">作业与考试分数</h4>';
document.body.appendChild(scoreContainer);
// 定义 API URL
const activityReadsUrl = `https://courses.zju.edu.cn/api/course/${courseId}/activity-reads-for-user`;
const homeworkScoresUrl = `https://courses.zju.edu.cn/api/course/${courseId}/homework-scores?fields=id,title`;
const examScoresUrl = `https://courses.zju.edu.cn/api/courses/${courseId}/exam-scores?no-intercept=true`;
const examsUrl = `https://courses.zju.edu.cn/api/courses/${courseId}/exams`;
// 同时获取四个 API 的数据
Promise.all([
fetch(activityReadsUrl).then(response => {
if (!response.ok) {
throw new Error(`无法获取 activity_reads 数据: ${response.statusText}`);
}
return response.json();
}),
fetch(homeworkScoresUrl).then(response => {
if (!response.ok) {
throw new Error(`无法获取 homework_scores 数据: ${response.statusText}`);
}
return response.json();
}),
fetch(examScoresUrl).then(response => {
if (!response.ok) {
throw new Error(`无法获取 exam_scores 数据: ${response.statusText}`);
}
return response.json();
}),
fetch(examsUrl).then(response => {
if (!response.ok) {
throw new Error(`无法获取 exams 数据: ${response.statusText}`);
}
return response.json();
})
])
.then(([activityReadsData, homeworkScoresData, examScoresData, examsData]) => {
displayScores(activityReadsData, homeworkScoresData, examScoresData, examsData);
})
.catch(error => {
console.error('获取数据时出错:', error);
scoreContainer.innerHTML += `<p style="color: red; text-align: center; margin: 0;">获取分数数据时出错</p>`;
});
function displayScores(activityReadsData, homeworkScoresData, examScoresData, examsData) {
const activityReads = activityReadsData.activity_reads;
const homeworkActivities = homeworkScoresData.homework_activities;
const examScores = examScoresData.exam_scores;
const exams = examsData.exams;
if ((!activityReads || activityReads.length === 0) && (!examScores || examScores.length === 0)) {
scoreContainer.innerHTML += '<p style="text-align: center; margin: 0;"><strong>当前课程没有作业或考试数据。</strong></p>';
return;
}
if ((!homeworkActivities || homeworkActivities.length === 0) && (!exams || exams.length === 0)) {
scoreContainer.innerHTML += '<p style="text-align: center; margin: 0;"><strong>没有作业或考试活动标题数据。</strong></p>';
return;
}
// 创建一个活动 ID 到作业标题的映射
const homeworkMap = new Map();
if (homeworkActivities && homeworkActivities.length > 0) {
homeworkActivities.forEach(activity => {
homeworkMap.set(activity.id, activity.title);
});
}
// 创建一个活动 ID 到考试标题的映射
const examsMap = new Map();
if (exams && exams.length > 0) {
exams.forEach(exam => {
examsMap.set(exam.id, exam.title);
});
}
// 创建一个活动 ID 到作业分数的映射(来自 activity_reads-for-user)
const homeworkScoresMap = new Map();
if (activityReads && activityReads.length > 0) {
activityReads.forEach(activityRead => {
const activityId = activityRead.activity_id;
if (homeworkMap.has(activityId)) { // 仅处理在 homeworkMap 中存在的 activity_id
const score = (activityRead.data && activityRead.data.score !== undefined && activityRead.data.score !== null) ? activityRead.data.score : '—';
homeworkScoresMap.set(activityId, score);
}
});
}
// 创建一个活动 ID 到考试分数的映射(来自 exam-scores)
const examScoresMap = new Map();
if (examScores && examScores.length > 0) {
examScores.forEach(examScore => {
const activityId = examScore.activity_id;
if (activityId !== 0) { // 过滤掉无效的 activity_id
const score = (examScore.score !== undefined && examScore.score !== null) ? examScore.score : '—';
examScoresMap.set(activityId, score);
}
});
}
// 合并作业和考试的 activity_id
const combinedActivityIds = new Set([
...homeworkScoresMap.keys(),
...examScoresMap.keys()
]);
if (combinedActivityIds.size === 0) {
scoreContainer.innerHTML += '<p style="text-align: center; margin: 0;"><strong>没有匹配的作业或考试数据。</strong></p>';
return;
}
// 按 activity_id 升序排序
const sortedActivityIds = Array.from(combinedActivityIds).sort((a, b) => a - b);
// 使用表格布局来紧凑显示
const table = document.createElement('table');
table.style.width = '100%';
table.style.borderCollapse = 'collapse';
sortedActivityIds.forEach(activityId => {
let title = '';
let score = '—';
if (homeworkScoresMap.has(activityId)) {
title = homeworkMap.get(activityId) || `作业 ID ${activityId}`;
score = homeworkScoresMap.get(activityId);
} else if (examScoresMap.has(activityId)) {
title = examsMap.get(activityId) || `考试 ID ${activityId}`;
score = examScoresMap.get(activityId);
}
const row = document.createElement('tr');
const titleCell = document.createElement('td');
titleCell.textContent = title;
titleCell.style.padding = '4px 6px';
titleCell.style.borderBottom = '1px solid #eee';
titleCell.style.width = '70%';
titleCell.style.wordBreak = 'break-word';
titleCell.style.fontWeight = 'bold';
titleCell.style.fontSize = '13px';
const scoreCell = document.createElement('td');
scoreCell.textContent = score;
scoreCell.style.padding = '4px 6px';
scoreCell.style.borderBottom = '1px solid #eee';
scoreCell.style.textAlign = 'right';
scoreCell.style.width = '30%';
scoreCell.style.fontSize = '13px';
scoreCell.style.color = score === '—' ? '#999' : '#000';
row.appendChild(titleCell);
row.appendChild(scoreCell);
table.appendChild(row);
});
scoreContainer.appendChild(table);
}
})();