惠州学院 HZU | 教务系统查分助手

没有任何引流和诱导,不需要手动输入代码,安装后访问学校教务系统,然后进入"学籍预警查询"或查分助手即可查询成绩信息。同时在主菜单添加快速入口。

// ==UserScript==
// @name         惠州学院 HZU | 教务系统查分助手
// @namespace    http://tampermonkey.net/
// @version      1.2.1
// @description  没有任何引流和诱导,不需要手动输入代码,安装后访问学校教务系统,然后进入"学籍预警查询"或查分助手即可查询成绩信息。同时在主菜单添加快速入口。
// @author       [email protected]
// @license      Apache License 2.0
// @match        https://jwxt.hzu.edu.cn/xjyj/*
// @match        https://jwxt.hzu.edu.cn/xtgl/*
// @homepage     https://greasyfork.org/en/scripts/500594
// @homepage     https://github.com/Ckrvxr
// ==/UserScript==

(function() {
    'use strict';

    function addMainMenuItem() {
        // 电脑版菜单添加
        const navMenu = document.querySelector('#cdNav .nav.navbar-nav');
        if (navMenu) {
            const navItem = document.createElement('li');
            navItem.className = 'dropdown';
            navItem.style.marginLeft = '8px';
            navItem.innerHTML = `
                <a href="https://jwxt.hzu.edu.cn/xjyj/xjyj_cxXjyjIndex.html?gnmkdm=N105505&layout=default"
                   style="font-weight:bold;color:#8fbda0;box-shadow:inset 0 0 0 3px #8fbda0">
                   查分助手
                </a>`;
            navMenu.appendChild(navItem);
        }

        // 手机版菜单添加
        const infoQuerySection = [...document.querySelectorAll('#app-page .app')].find(app => app.querySelector('.title span')?.textContent === '信息查询');
        if (infoQuerySection) {
            const boxs = infoQuerySection.querySelector('.boxs');
            if (boxs && !boxs.querySelector('a[href*="xjyj_cxXjyjIndex"]')) {
                const newBox = document.createElement('div');
                newBox.className = 'box app-box';
                newBox.style.margin = '8px';
                newBox.innerHTML = `
                    <a href="https://jwxt.hzu.edu.cn/xjyj/xjyj_cxXjyjIndex.html?gnmkdm=N105505&layout=default"
                       style="text-decoration:none;color:#333;display:block;text-align:center">
                        <img src="https://em-content.zobj.net/source/animated-noto-color-emoji/427/hundred-points_1f4af.gif"  alt="💯">
                        <p>查分助手</p>
                    </a>`;
                boxs.appendChild(newBox);
            }
        }
    }

    function handleScoreQuery() {
        const SCORE_API_URL = "https://jwxt.hzu.edu.cn/xjyj/xjyj_cxXjyjjdlb.html";

        // 获取表单数据
        function getFormData() {
            const formData = {
                jg_id: document.getElementById("jg_id")?.value || null,
                njdm_id: document.getElementById("njdm_id")?.value || null,
                zyh_id: document.getElementById("zyh_id")?.value || null
            };
            console.log("[教务系统查分助手] 表单数据:", formData);
            return formData;
        }

        // 发起成绩请求
        function fetchScores(formData) {
            const queryParams = new URLSearchParams(formData).toString();
            console.log("[教务系统查分助手] 请求地址:", SCORE_API_URL);
            console.log("[教务系统查分助手] 请求参数:", queryParams);

            return fetch(SCORE_API_URL, {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
                },
                body: queryParams
            }).then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP 错误! 状态码: ${response.status}`);
                }
                return response.json();
            }).then(data => {
                console.log("[教务系统查分助手] API 响应数据:", data);
                return data;
            });
        }

        // 处理成绩数据
        function processScoreData(scoreData) {
            console.log("[教务系统查分助手] 开始处理原始数据:", scoreData);

            let courses = [];
            try {
                scoreData.forEach(semester => {
                    semester.kcList && semester.kcList.forEach(course => {
                        if (course.kcmc && course.bfzcj) {
                            courses.push({
                                name: course.kcmc,
                                score: parseFloat(course.bfzcj)
                            });
                        }
                    });
                });
            } catch (e) {
                console.error("[教务系统查分助手] 解析成绩数据失败:", e);
            }

            console.log("[教务系统查分助手] 解析完成,共找到课程数:", courses.length);
            console.log("[教务系统查分助手] 解析后的课程列表:", courses);

            return courses.sort((a, b) => a.name.localeCompare(b.name, "zh-CN"));
        }

        // 生成 HTML 显示内容
        function generateScoreHTML(courses) {
            return courses.map(course => `
                <div class="course-score ${course.score < 60 ? "fail" : "pass"}">
                    <span class="course-name">${course.name}</span>
                    <span class="course-score-value">${course.score}</span>
                </div>
            `).join("");
        }

        // 显示模态框
        function showScoreModal(htmlContent, isError = false, coursesData = null) {
            let allCount = 0, passCount = 0, failCount = 0;
            for (let course of coursesData) {
                const score = parseFloat(course.score);
                if (!isNaN(score)) {
                    allCount++;
                    if (score >= 60) {
                        passCount++;
                    } else {
                        failCount++;
                    }
                }
            }

            console.log("[教务系统查分助手] 显示模态框:", { allCount, passCount, failCount });

            const modal = document.createElement("div");
            modal.id = "custom-modal";
            modal.innerHTML = `
                <div class="modal-content">
                    <h2 class="modal-title">${isError ? "错误" : "您的成绩"}</h2>
                    <div class="filter-buttons">
                        <button class="filter-btn active" data-filter="all">全部 (${allCount})</button>
                        <button class="filter-btn" data-filter="pass">及格 (${passCount})</button>
                        <button class="filter-btn" data-filter="fail">不及格 (${failCount})</button>
                    </div>
                    <div class="scores-container">${htmlContent}</div>
                    <button class="close-btn">×</button>
                </div>
            `;
            document.body.appendChild(modal);

            const closeButton = modal.querySelector(".close-btn");
            closeButton.onclick = () => modal.remove();

            if (!isError) {
                const filterButtons = modal.querySelectorAll(".filter-btn");
                filterButtons.forEach(button => {
                    button.onclick = () => {
                        filterButtons.forEach(btn => btn.classList.remove("active"));
                        button.classList.add("active");
                        const filter = button.dataset.filter;
                        modal.querySelectorAll(".course-score").forEach(courseElement => {
                            courseElement.style.display =
                                filter === "all" ? "flex" :
                                filter === "pass" ? (courseElement.classList.contains("pass") ? "flex" : "none") :
                                (courseElement.classList.contains("fail") ? "flex" : "none");
                        });
                    };
                });
            }
        }

        // 添加样式
        function addModalStyles() {
            const styleElement = document.createElement("style");
            styleElement.textContent = `
                #custom-modal {
                    position: fixed;
                    left: 0;
                    top: 0;
                    width: 100%;
                    height: 100%;
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    z-index: 10000;
                    backdrop-filter: blur(10px) saturate(180%);
                }
                .modal-content {
                    background-color: #fff;
                    border-radius: 10px;
                    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
                    width: 90%;
                    max-width: 500px;
                    max-height: 80vh;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                    position: relative;
                }
                .modal-title {
                    font-size: 24px;
                    color: #333;
                    margin: 0;
                    padding: 20px;
                    background-color: #fff;
                    border-bottom: 2px solid #f0f0f0;
                    text-align: center;
                    position: sticky;
                    top: 0;
                    z-index: 2;
                }
                .filter-buttons {
                    display: flex;
                    justify-content: center;
                    gap: 10px;
                    padding: 10px 20px;
                    background-color: #f8f9fa;
                    border-bottom: 1px solid #e9ecef;
                }
                .filter-btn {
                    padding: 6px 12px;
                    border: none;
                    border-radius: 4px;
                    background-color: #e9ecef;
                    color: #495057;
                    cursor: pointer;
                    transition: all 0.3s ease;
                    font-size: 14px;
                }
                .filter-btn:hover {
                    background-color: #dee2e6;
                }
                .filter-btn.active {
                    background-color: #8fbda0;
                    color: white;
                    font-weight: bold;
                }
                .scores-container {
                    padding: 20px;
                    overflow-y: auto;
                    flex-grow: 1;
                }
                .course-score {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 12px 15px;
                    margin-bottom: 10px;
                    border-radius: 5px;
                    transition: all 0.3s ease;
                }
                .course-score:hover {
                    transform: translateY(-3px);
                    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
                }
                .course-score.pass {
                    background-color: #e8f5e9;
                    border-left: 4px solid #4caf50;
                }
                .course-score.fail {
                    background-color: #ffebee;
                    border-left: 4px solid #f44336;
                }
                .course-name {
                    font-weight: bold;
                    color: #333;
                    flex-grow: 1;
                    margin-right: 10px;
                }
                .course-score-value {
                    font-weight: bold;
                    font-size: 18px;
                }
                .pass .course-score-value {
                    color: #4caf50;
                }
                .fail .course-score-value {
                    color: #f44336;
                }
                .close-btn {
                    position: absolute;
                    top: 10px;
                    right: 10px;
                    border: none;
                    background: none;
                    font-size: 48px;
                    color: #999;
                    cursor: pointer;
                    transition: color 0.3s ease;
                    z-index: 20020;
                }
                .close-btn:hover {
                    color: #fc4e4e;
                }
                .scores-container::-webkit-scrollbar {
                    width: 8px;
                }
                .scores-container::-webkit-scrollbar-track {
                    background: #f1f1f1;
                    border-radius: 4px;
                }
                .scores-container::-webkit-scrollbar-thumb {
                    background: #888;
                    border-radius: 4px;
                }
                .scores-container::-webkit-scrollbar-thumb:hover {
                    background: #555;
                }
            `;
            document.head.appendChild(styleElement);
        }

        // 初始化查分逻辑
        function ScoreQuery() {
            console.log("[教务系统查分助手] 开始查分"); // 🧪 调试输出

            const formData = getFormData();
            fetchScores(formData)
                .then(processScoreData)
                .then(courses => {
                    const htmlContent = generateScoreHTML(courses);
                    showScoreModal(htmlContent, false, courses);
                })
                .catch(error => {
                    console.error("[教务系统查分助手] 请求或处理出错:", error); // 🧪 调试输出
                    showScoreModal(`错误: ${error.message}`, true);
                });
        }

        addModalStyles();
        ScoreQuery();
    }

    if (window.location.href.includes('xtgl/')) {
        addMainMenuItem();
    } else if (window.location.href.includes('xjyj/')) {
        window.addEventListener("load", handleScoreQuery);
    }
})();