// ==UserScript==
// @name 课程分数信息查看(中南林)-电脑端美化版
// @namespace http://tampermonkey.net/
// @version 0.1.4
// @description 显示综合成绩信息分配比例与成绩,扩展可点击区域,优化布局。新增提示判断课程是否分对综合成绩进行划分
// @match http://jwgl.webvpn.csuft.edu.cn/jsxsd/kscj/*
// @match http://jwgl.csuft.edu.cn/jsxsd/kscj/*
// @run-at document-end
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @license GNU GPLv3
// ==/UserScript==
(function() {
'use strict';
// 添加全局样式
GM_addStyle(`
.info-container {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.info-marker {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
transition: all 0.2s ease;
font-size: 0.9em;
font-weight: bold;
}
.info-marker.has-info {
background-color: rgba(40, 167, 69, 0.1);
color: #28a745;
}
.info-marker.no-info {
background-color: rgba(220, 53, 69, 0.1);
color: #dc3545;
}
.info-marker:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.info-marker.has-info:hover {
background-color: rgba(40, 167, 69, 0.2);
}
.info-marker.no-info:hover {
background-color: rgba(220, 53, 69, 0.2);
}
.info-loader {
display: inline-flex;
align-items: center;
gap: 4px;
color: #6c757d;
font-size: 0.9em;
margin-top: 4px;
}
.info-score {
font-weight: bold;
color: #007bff;
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
transition: all 0.2s ease;
}
.info-score:hover {
background-color: rgba(0, 123, 255, 0.1);
transform: translateY(-1px);
}
`);
// 提取URL的正则表达式
function extractUrl(jsCode) {
if (!jsCode) return null;
try {
if (jsCode.startsWith('http')) return jsCode;
var matches = jsCode.match(/openWindow\('([^']+)/);
return matches && matches[1] ? matches[1] : null;
} catch (error) {
console.warn('URL提取错误:', error);
return null;
}
}
// 创建可点击标记
function createClickableMarker(cell, hasInfo, url) {
// 移除旧标记
var oldMarkers = cell.querySelectorAll('.info-container');
for (var i = 0; i < oldMarkers.length; i++) {
var marker = oldMarkers[i];
if (marker.parentNode === cell) {
cell.removeChild(marker);
}
}
// 创建容器
var container = document.createElement('div');
container.className = 'info-container';
// 创建成绩显示
var scoreDisplay = document.createElement('div');
scoreDisplay.className = 'info-score';
// 获取原始分数
var originalScore = cell.querySelector('a font[color="blue"]');
var scoreValue = originalScore ? originalScore.textContent : '?';
scoreDisplay.textContent = scoreValue;
scoreDisplay.addEventListener('click', function(e) {
e.stopPropagation();
if (url) openDetailsPage(url);
});
// 创建标记
var marker = document.createElement('div');
marker.className = 'info-marker ' + (hasInfo ? 'has-info' : 'no-info');
// 创建图标
var icon = document.createElement('span');
icon.textContent = hasInfo ? '✓' : '✕';
// 创建文本
var text = document.createElement('span');
text.textContent = hasInfo ? '有' : '无';
// 添加点击事件
if (url) {
marker.addEventListener('click', function(e) {
e.stopPropagation();
openDetailsPage(url);
});
}
// 组装元素
marker.appendChild(icon);
marker.appendChild(text);
container.appendChild(scoreDisplay);
container.appendChild(marker);
cell.appendChild(container);
// 移除原始链接
var originalLink = cell.querySelector('a');
if (originalLink) {
originalLink.style.display = 'none';
}
return container;
}
// 打开详情页
function openDetailsPage(url) {
if (typeof unsafeWindow.openWindow === 'function') {
unsafeWindow.openWindow(url, 700, 500);
} else {
window.open(url, '_blank', 'width=700,height=500');
}
}
// 主处理函数
function processPage() {
try {
// 1. 恢复注释中的成绩链接
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null, false);
var toRemove = [];
var node;
while ((node = walker.nextNode())) {
try {
var content = node.nodeValue.trim();
if (content.startsWith('<td') && content.endsWith('</td>')) {
content = content.replace(/&/g, '&');
var range = document.createRange();
range.setStartAfter(node);
var frag = range.createContextualFragment(content);
node.parentNode.insertBefore(frag, node);
toRemove.push(node);
}
} catch (e) {
console.warn('恢复注释节点时出错:', e);
}
}
toRemove.forEach(function(n) { n.remove(); });
// 2. 隐藏原始不可点击的成绩
var dataTable = document.getElementById('dataList');
if (!dataTable) return;
var rows = dataTable.rows;
for (var i = 0; i < rows.length; i++) {
try {
var cells = rows[i].cells;
if (cells.length > 5 && cells[5]) {
cells[5].style.display = 'none';
}
} catch (e) {
console.warn('处理行时出错:', e);
}
}
// 3. 新增:智能检测信息状态
detectInfoStatus();
} catch (error) {
console.error('主处理函数出错:', error);
}
}
// 修改detectInfoStatus函数
function detectInfoStatus() {
try {
var scoreCells = document.querySelectorAll('#dataList tr td:nth-child(5)');
var requestQueue = [];
for (var i = 0; i < scoreCells.length; i++) {
try {
var cell = scoreCells[i];
var link = cell.querySelector('a');
if (!link || link.dataset.checked) continue;
requestQueue.push({ cell: cell, link: link });
} catch (e) {
console.warn('添加请求到队列时出错:', e);
}
}
// 顺序处理队列
var processNext = function(index) {
if (index >= requestQueue.length) {
return;
}
try {
var item = requestQueue[index];
var cell = item.cell;
var link = item.link;
if (!document.body.contains(cell)) {
setTimeout(function() { processNext(index + 1); }, 300);
return;
}
link.dataset.checked = 'true';
// 获取实际URL
var jsCode = link.getAttribute('href');
var actualUrl = extractUrl(jsCode);
// 处理无效URL
if (!actualUrl) {
createClickableMarker(cell, false);
setTimeout(function() { processNext(index + 1); }, 300);
return;
}
// 修正URL
var correctedUrl = actualUrl.replace(/&/g, '&');
// 发送请求
GM_xmlhttpRequest({
method: "GET",
url: correctedUrl,
onload: function(response) {
try {
if (!document.body.contains(cell)) return;
// 检测信息存在性
var hasInfo = /(期末成绩|平时成绩比例|成绩分类)/.test(response.responseText);
createClickableMarker(cell, hasInfo, correctedUrl);
setTimeout(function() { processNext(index + 1); }, 300);
} catch (e) {
console.error('请求处理错误:', e);
createClickableMarker(cell, false);
setTimeout(function() { processNext(index + 1); }, 300);
}
},
onerror: function() {
createClickableMarker(cell, false);
setTimeout(function() { processNext(index + 1); }, 300);
},
ontimeout: function() {
createClickableMarker(cell, false);
setTimeout(function() { processNext(index + 1); }, 300);
},
timeout: 5000
});
} catch (error) {
console.error('处理请求队列时出错:', error);
setTimeout(function() { processNext(index + 1); }, 300);
}
};
// 启动处理队列
processNext(0);
} catch (error) {
// 不再调用 hideTableLoader
}
}
// 初始执行
try {
processPage();
} catch (error) {
console.error('脚本初始化错误:', error);
}
// 监听动态变化
var observer = new MutationObserver(function(mutations) {
var needsUpdate = false;
for (var i = 0; i < mutations.length; i++) {
if (mutations[i].addedNodes.length) {
needsUpdate = true;
break;
}
}
if (needsUpdate) {
try {
processPage();
} catch (error) {
console.error('MutationObserver处理错误:', error);
}
}
});
try {
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
} catch (error) {
console.error('MutationObserver初始化错误:', error);
}
})();