广东工业大学教务系统学生学分计算工具

窝工的教务系统的绩点计算工具😆

目前為 2025-01-26 提交的版本,檢視 最新版本

// ==UserScript==
// @name         广东工业大学教务系统学生学分计算工具
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  窝工的教务系统的绩点计算工具😆
// @author       GamerNoTitle
// @match        https://jxfw.gdut.edu.cn/*
// @grant        GM_addStyle
// @run-at       document-idle
// @homepageURL  https://github.com/GDUTMeow/GPACalculator
// @supportURL   https://github.com/GDUTMeow/GPACalculator/issues
// @license      GPLv3
// ==/UserScript==

/* 功能特性
✅ 自动注入计算按钮
✅ 支持免修课程计算(按3.0绩点)
✅ 实时监控页面变化
✅ 适配SPA路由跳转
✅ 双模式计算结果展示

📌 使用说明
1. 访问教务系统成绩页面
2. 点击工具栏的"📊 计算绩点"按钮
3. 查看弹窗中的详细计算结果
*/

(function() {
    'use strict';

    // 按钮自定义样式
    GM_addStyle(`
        #calcGPA {
            margin-left: 12px;
            padding: 2px 8px;
            background: #5bc0de;
            color: white;
            border: 1px solid #46b8da;
            border-radius: 3px;
            cursor: pointer;
            font-size: 12px;
            vertical-align: middle;
            transition: all 0.3s;
        }
        #calcGPA:hover {
            background: #31b0d5;
            transform: translateY(-1px);
        }
        #calcGPA:active {
            transform: translateY(0);
        }
    `);

    // 核心注入函数
    function injectButton() {
        // 如果按钮已存在则终止
        if (document.getElementById('calcGPA')) return;

        // 查找目标容器
        const toolbar = document.getElementById('tb');
        if (!toolbar) return;

        // 验证是否在成绩页面
        const scoreTable = document.querySelector('table.datagrid-btable');
        if (!scoreTable) return;

        // 定位插入点
        const targetRow = toolbar.querySelector('tr');
        if (!targetRow) return;

        // 创建容器TD
        const buttonCell = document.createElement('td');
        buttonCell.style.paddingLeft = '15px';
        buttonCell.style.position = 'relative';
        buttonCell.style.top = '-1px'; // 微调垂直对齐

        // 创建按钮
        const button = document.createElement('a');
        button.id = 'calcGPA';
        button.innerHTML = '📊 计算绩点';
        button.onclick = calculateGPA;

        // 组装元素
        buttonCell.appendChild(button);
        targetRow.appendChild(buttonCell);

        console.log('[成功] 绩点按钮已注入');
    }

    // 绩点计算函数
    function calculateGPA() {
        const table = document.querySelector('table.datagrid-btable');
        if (!table) {
            alert('错误:未找到成绩表格');
            return;
        }

        // 用于计算包含免修的数据
        let totalCredits = 0;
        let weightedSum = 0;
        let totalCreditsWithExemption = 0;
        let weightedSumWithExemption = 0;

        table.querySelectorAll('tr').forEach(row => {
            if (row.querySelector('th')) return;

            const creditCell = row.querySelector('td[field="xf"] div');
            const gradeCell = row.querySelector('td[field="cjjd"] div');
            if (!creditCell || !gradeCell) return;

            // 解析数据
            const credits = parseFloat(creditCell.textContent.trim());
            const gradeText = gradeCell.textContent.trim();
            const isExempt = gradeText === '免修' || gradeText === '--';

            if (isNaN(credits)) return;

            // 原始计算(排除免修)
            if (!isExempt) {
                const grade = parseFloat(gradeText);
                if (!isNaN(grade)) {
                    totalCredits += credits;
                    weightedSum += grade * credits;
                }
            }

            // 包含免修的计算(按3.0计算)
            const effectiveGrade = isExempt ? 3.0 : parseFloat(gradeText);
            if (!isNaN(effectiveGrade)) {
                totalCreditsWithExemption += credits;
                weightedSumWithExemption += effectiveGrade * credits;
            }
        });

        // 计算结果
        const finalGPA = totalCredits > 0
            ? (weightedSum / totalCredits).toFixed(2)
            : 0;

        const finalGPAWithExemption = totalCreditsWithExemption > 0
            ? (weightedSumWithExemption / totalCreditsWithExemption).toFixed(2)
            : 0;

        // 显示学分
        alert([
            `=========== 学分计算小工具 ============`,
            `✅ 总学分(不含免修):${totalCredits}`,
            `🚩 加权总分(不含免修):${weightedSum.toFixed(2)}`,
            `🎉 最终绩点(不含免修):${finalGPA}`,
            `----------------------------------------------------------`,
            `✅ 总学分(含免修):${totalCreditsWithExemption}`,
            `🚩 加权总分(含免修):${weightedSumWithExemption.toFixed(2)}`,
            `🎉 最终绩点(含免修):${finalGPAWithExemption}`,
            `----------------------------------------------------------`,
            `⚠️ 不含免修的是教务系统里面的计算方式`,
            `⚠️ 含免修的是GDUTDays的计算方式`,
            `有空的话可以给个Star✨`,
            `https://github.com/GDUTMeow/GPACalculator`,
            `=============== v1.1 ================`
        ].join('\n'));
    }
    // 智能检测系统
    let observer;
    function initObserver() {
        // 如果已有观察器则重置
        if (observer) observer.disconnect();

        // 创建新的观察器
        observer = new MutationObserver(mutations => {
            injectButton();
        });

        // 配置观察选项
        const config = {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        };

        // 开始观察body变化
        if (document.body) {
            observer.observe(document.body, config);
            console.log('[系统] 启动DOM观察器');
        }
    }

    // URL变化检测
    let lastUrl = location.href;
    setInterval(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            console.log('[系统] 检测到URL变化,重新初始化');
            initObserver();
            injectButton();
        }
    }, 1000);

    // 页面加载处理
    if (document.readyState === 'complete') {
        initObserver();
        setTimeout(injectButton, 1500); // 延迟注入
    } else {
        window.addEventListener('load', () => {
            initObserver();
            setTimeout(injectButton, 1500);
        });
    }

    // 兼容SPA路由
    window.addEventListener('popstate', () => {
        setTimeout(injectButton, 300);
    });
})();