// ==UserScript==
// @name 南理工教务增强助手
// @namespace http://tampermonkey.net/
// @version 1.2
// @description 在合适的地方显示课程大纲、选修课类别及选修课学分情况
// @match 202.119.81.112/*
// @match bkjw.njust.edu.cn/*
// @match 202.119.81.112:9080/*
// @match 202.119.81.113:9080/*
// @grant GM_xmlhttpRequest
// @connect jsdelivr.net
// @author Light
// @license MIT
// @supportURL https://github.com/NJUST-OpenLib/NJUST-JWC-Enhance
// ==/UserScript==
(function () {
'use strict';
const CATEGORY_URL = 'https://fastly.jsdelivr.net/gh/NJUST-OpenLib/NJUST-JWC-Enhance@latest/data/xxk.json';
const OUTLINE_URL = 'https://fastly.jsdelivr.net/gh/NJUST-OpenLib/NJUST-JWC-Enhance@latest/data/kcdg.json';
let courseCategoryMap = {};
let courseOutlineMap = {};
// 统一弹窗样式函数
function createUnifiedModal(title, content, type = 'info') {
// 移除可能存在的旧弹窗
const existingModal = document.getElementById('njustAssistantModal');
if (existingModal) {
existingModal.remove();
}
const container = document.createElement('div');
container.id = 'njustAssistantModal';
// 根据类型设置不同的渐变色
let gradientColor;
switch (type) {
case 'warning':
gradientColor = 'linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%)';
break;
case 'success':
gradientColor = 'linear-gradient(135deg, #28a745 0%, #20c997 100%)';
break;
case 'info':
default:
gradientColor = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
break;
}
container.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: ${gradientColor};
border: none;
border-radius: 15px;
padding: 0;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
z-index: 10000;
min-width: 200px;
max-width: 500px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow: hidden;
animation: fadeIn 0.3s ease-out;
`;
container.innerHTML = `
${content}
⚠️ 免责声明
本工具仅为学习交流使用,数据仅供参考。请以教务处官网信息为准,使用本工具产生的任何后果由用户自行承担。
`;
// 添加CSS动画
if (!document.getElementById('njustAssistantStyles')) {
const style = document.createElement('style');
style.id = 'njustAssistantStyles';
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
`;
document.head.appendChild(style);
}
// 添加拖动功能
addDragFunctionality(container);
document.body.appendChild(container);
return container;
}
// 拖动功能
function addDragFunctionality(container) {
let isDragging = false;
let currentX, currentY, initialX, initialY;
let xOffset = 0, yOffset = 0;
const dragHandle = container.querySelector('#dragHandle');
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragHandle || dragHandle.contains(e.target)) {
isDragging = true;
}
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
}
function drag(e) {
if (isDragging) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
container.style.transform = `translate(${currentX}px, ${currentY}px)`;
}
}
dragHandle.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
dragHandle.addEventListener('touchstart', dragStart);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', dragEnd);
}
// 检测强智科技页面
function checkQiangzhiPage() {
const currentUrl = window.location.href;
const pageTitle = document.title;
// 检测是否为强智科技页面且无法登录
if (
pageTitle.includes('强智科技教务系统概念版')) {
const content = `
🚫 该页面无法登录
检测到您正在访问强智科技教务系统概念版,该页面无法正常登录。
请转向以下正确的登录页面:
💡 提示:请使用上述链接进行登录,登录后可正常使用教务系统功能
`;
createUnifiedModal('南理工教务助手', content, 'warning');
return true;
}
return false;
}
function loadJSON(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
onload: function (response) {
try {
const json = JSON.parse(response.responseText);
resolve(json);
} catch (e) {
console.error(`解析 JSON 失败: ${url}`, e);
reject(e);
}
},
onerror: function (err) {
console.error(`加载失败: ${url}`, err);
reject(err);
}
});
});
}
function buildCourseMaps(categoryList, outlineList) {
categoryList.forEach(item => {
if (item.course_code && item.category) {
courseCategoryMap[item.course_code.trim()] = item.category;
}
});
outlineList.forEach(item => {
if (item.course_code && item.id) {
courseOutlineMap[item.course_code.trim()] = item.id;
}
});
}
function createCreditSummaryWindow() {
// 使用统一的弹窗样式,但保持原有的固定位置和拖动功能
const container = document.createElement('div');
container.id = 'creditSummaryWindow';
container.style.cssText = `
position: fixed;
top: 40px;
right: 40px;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 14px;
padding: 0;
box-shadow: 0 8px 32px rgba(0,0,0,0.13);
z-index: 9999;
min-width: 420px;
max-width: 520px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow: hidden;
`;
container.innerHTML = `
⚠️ 特别声明
选修课类别可能发生变化,仅供参考。
本工具可能因为教务处改版而不可靠,不对数据准确性负责
`;
// 添加拖动功能
let isDragging = false;
let currentX, currentY, initialX, initialY;
let xOffset = 0, yOffset = 0;
const dragHandle = container.querySelector('#creditDragHandle');
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragHandle || dragHandle.contains(e.target)) {
isDragging = true;
}
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
}
function drag(e) {
if (isDragging) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
container.style.transform = `translate(${currentX}px, ${currentY}px)`;
}
}
dragHandle.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
dragHandle.addEventListener('touchstart', dragStart);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', dragEnd);
document.body.appendChild(container);
return container;
}
function updateCreditSummary() {
const creditSummaryDiv = document.getElementById('creditSummary');
if (!creditSummaryDiv) return;
const creditsByType = {}; // 按课程类型(通识教育课等)统计
const creditsByCategory = {}; // 按选修课类别统计
const tables = document.querySelectorAll('table');
tables.forEach(table => {
const rows = table.querySelectorAll('tr');
rows.forEach(row => {
const tds = row.querySelectorAll('td');
if (tds.length >= 11) {
const courseCode = tds[2].textContent.trim();
const credit = parseFloat(tds[6].textContent) || 0;
const courseType = tds[10].textContent.trim(); // 课程类型(通识教育课等)
// 从页面上已显示的类别信息中提取选修课类别
const categoryDiv = tds[2].querySelector('[data-category-inserted]');
let category = null;
if (categoryDiv) {
// 直接获取文本内容,因为现在只显示类别名称
category = categoryDiv.textContent.trim();
// 如果文本为空或者不是有效的类别,则设为null
if (!category || category.length === 0) {
category = null;
}
}
// 按课程类型统计
if (courseType) {
if (!creditsByType[courseType]) {
creditsByType[courseType] = {
credits: 0,
count: 0
};
}
creditsByType[courseType].credits += credit;
creditsByType[courseType].count += 1;
}
// 按选修课类别统计
if (category) {
if (!creditsByCategory[category]) {
creditsByCategory[category] = {
credits: 0,
count: 0
};
}
creditsByCategory[category].credits += credit;
creditsByCategory[category].count += 1;
}
}
});
});
// 计算总计
const totalCreditsByType = Object.values(creditsByType).reduce((sum, data) => sum + data.credits, 0);
const totalCountByType = Object.values(creditsByType).reduce((sum, data) => sum + data.count, 0);
const totalCreditsByCategory = Object.values(creditsByCategory).reduce((sum, data) => sum + data.credits, 0);
const totalCountByCategory = Object.values(creditsByCategory).reduce((sum, data) => sum + data.count, 0);
// 生成HTML - 表格样式布局
let summaryHTML = '';
summaryHTML += '
📊 按课程类型统计
';
// 总计行
summaryHTML += `
总计
${totalCreditsByType.toFixed(1)} 学分
${totalCountByType} 门
`;
// 课程类型表格
summaryHTML += '
';
for (const [type, data] of Object.entries(creditsByType)) {
summaryHTML += `
${type}
${data.credits.toFixed(1)} 学分
${data.count} 门
`;
}
summaryHTML += '
';
summaryHTML += '
';
if (Object.keys(creditsByCategory).length > 0) {
summaryHTML += '';
summaryHTML += '
🏷️ 按选修课类别统计
';
// 总计行
summaryHTML += `
总计
${totalCreditsByCategory.toFixed(1)} 学分
${totalCountByCategory} 门
`;
// 选修课类别表格
summaryHTML += '
';
for (const [category, data] of Object.entries(creditsByCategory)) {
summaryHTML += `
${category}
${data.credits.toFixed(1)} 学分
${data.count} 门
`;
}
summaryHTML += '
';
}
summaryHTML += '
';
creditSummaryDiv.innerHTML = summaryHTML || '暂无数据';
}
function processAllTables() {
const tables = document.querySelectorAll('table');
const isGradePage = window.location.pathname.includes('/njlgdx/kscj/cjcx_list');
const isSchedulePage = window.location.pathname.includes('xskb_list.do') &&
document.title.includes('学期理论课表');
tables.forEach(table => {
// 如果是课表页面,只处理 id="dataList" 的表格
if (isSchedulePage && table.id !== 'dataList') {
return;
}
const rows = table.querySelectorAll('tr');
rows.forEach(row => {
const tds = row.querySelectorAll('td');
if (tds.length < 3) return;
let courseCodeTd;
let courseCode;
if (isGradePage) {
courseCodeTd = tds[2]; // 成绩页面课程代码在第3列
courseCode = courseCodeTd.textContent.trim();
} else if (isSchedulePage) {
courseCodeTd = tds[1]; // 课表页面课程代码在第2列
courseCode = courseCodeTd.textContent.trim();
} else {
courseCodeTd = tds[1];
const parts = courseCodeTd.innerHTML.split('
');
if (parts.length === 2) {
courseCode = parts[1].trim();
} else {
return;
}
}
// 插入类别
if (!courseCodeTd.querySelector('[data-category-inserted]')) {
const category = courseCategoryMap[courseCode];
if (category) {
const catDiv = document.createElement('div');
catDiv.setAttribute('data-category-inserted', '1');
catDiv.style.color = '#28a745';
catDiv.style.fontWeight = 'bold';
catDiv.style.marginTop = '4px';
// 只显示类别名称,不显示前缀
catDiv.textContent = category;
courseCodeTd.appendChild(catDiv);
}
}
// 插入老师说明(来自 title,仅在非成绩页面和非课表页面)
if (!isGradePage && !isSchedulePage && courseCodeTd.title && !courseCodeTd.querySelector('[data-title-inserted]')) {
const titleDiv = document.createElement('div');
titleDiv.setAttribute('data-title-inserted', '1');
titleDiv.style.color = '#666';
titleDiv.style.fontSize = '13 px';
titleDiv.style.marginTop = '4px';
titleDiv.style.fontStyle = 'italic';
titleDiv.textContent = `📌 老师说明:${courseCodeTd.title}`;
courseCodeTd.appendChild(titleDiv);
}
// 插入课程大纲链接
if (!courseCodeTd.querySelector('[data-outline-inserted]')) {
const realId = courseOutlineMap[courseCode];
const outlineDiv = document.createElement('div');
outlineDiv.setAttribute('data-outline-inserted', '1');
outlineDiv.style.marginTop = '4px';
if (realId) {
const link = document.createElement('a');
link.href = `http://202.119.81.112:8080/kcxxAction.do?method=kcdgView&jx02id=${realId}&isentering=0`;
link.textContent = '📘 查看课程大纲';
link.target = '_blank';
link.style.color = '#0077cc';
outlineDiv.appendChild(link);
} else {
outlineDiv.textContent = '❌ 无大纲信息';
outlineDiv.style.color = 'gray';
}
courseCodeTd.appendChild(outlineDiv);
}
});
});
// 更新学分统计(仅在成绩页面)
if (isGradePage) {
updateCreditSummary();
}
}
async function init() {
try {
// 首先检测强智科技页面
if (checkQiangzhiPage()) {
return; // 如果是强智科技页面,显示提示后直接返回
}
const [categoryData, outlineData] = await Promise.all([
loadJSON(CATEGORY_URL),
loadJSON(OUTLINE_URL)
]);
buildCourseMaps(categoryData, outlineData);
// 如果是成绩页面,创建悬浮窗
if (window.location.pathname.includes('/njlgdx/kscj/cjcx_list')) {
createCreditSummaryWindow();
}
processAllTables();
const observer = new MutationObserver(() => {
// 每次页面变化时也检查强智科技页面
if (!checkQiangzhiPage()) {
processAllTables();
}
});
observer.observe(document.body, { childList: true, subtree: true });
} catch (err) {
console.error('初始化失败:', err);
}
}
setTimeout(init, 1000);
})();