哈尔滨理工大学 教务在线 评估课程&教学评价自动完成、个人GPA计算脚本 Hrbust Auto-Eva

哈尔滨理工大学(hrbust) 评估课程&教学评价自动完成、个人GPA计算脚本。作用于哈理工教务在线(http://jwzx.hrbust.edu.cn/)内。

// ==UserScript==
/* globals jQuery, $, waitForKeyElements */
// @require      https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @name         哈尔滨理工大学 教务在线 评估课程&教学评价自动完成、个人GPA计算脚本 Hrbust Auto-Eva
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  哈尔滨理工大学(hrbust) 评估课程&教学评价自动完成、个人GPA计算脚本。作用于哈理工教务在线(http://jwzx.hrbust.edu.cn/)内。
// @author       Leisurelybear
// @homepageURL  https://github.com/Leisurelybear/Hrbust-Auto-Eva
// @supportURL   https://github.com/Leisurelybear/Hrbust-Auto-Eva/issues
// @match        http://jwzx.hrbust.edu.cn/*
// @run-at       document-end
// @license      MIT
// @icon         https://s3.ax1x.com/2020/11/22/DG9DVe.png
// @icon64       https://s3.ax1x.com/2020/11/22/DG9DVe.png

// ==/UserScript==
(function () {
    "use strict";
    /////////////////////////---- Settings -----///////////////////////////
    var sleep_time = 1000; //设置模拟提交刷新时间,单位毫秒,不建议更改

    var auto_commit = 1; //设置自动提交  【1:自动提交, 0:手动提交】

    var evaluation_level = 0; //设置评价等级,默认为0:关闭。其他参数:【1:完全好评,2:中等评价,3:较差评价,4:完全随机。】 * 注意:开启此选项,则需要手动提交每一次评价

    //你认为教师在教学上最值得肯定之处? 可按照格式拼接或删除建议
    var text_approve = [
        "活跃同学气氛 让同学们都能积极参于课堂问答",
        "善待学生,师生融恰,学生成绩能稳定提高,学校、家长双满意即是值得肯定的优秀教师。",
        "我觉得我们学校的老师教学水平是非常值得肯定的,学校里有很多年龄比较大的老师,他们都有特别丰富的人生阅历和渊博的知识,这些完美的履历是可以让我们敬畏他们的。",
        "老师对学生的关心与帮助是无微不至的",
    ];

    //你认为教师在教学上最应该改进之处?可按照格式拼接或删除建议
    var text_improve = [
        "转变教育观念:传统教师与现代教师的区别应当有现代教育观,吸引新思想,树立新的教育观念。",
        "拓展教学艺术,教学课堂要有创新,教师首先要努力实现教学观念的更新,以前的教学注重以教材为中心,以教测为目标。",
        "很好",
        "很不错",
        "很完美",
    ];

    //你对本门课程选用教材及参考书有何意见和建议? 可按照格式拼接或删除建议
    var text_advice = [
        "很好",
        "教材应该具有实践性",
        "教材应该具有人文性",
        "教材应该具有综合性",
        "教材应该具有思想性",
    ];

    //其他建议?可按照格式拼接或删除建议
    var text_other = ["很好", "无"];

    /////////////////////////---- start -----///////////////////////////

    var URL_keyword_score = "studentOwnScore"; //学生个人成绩URL关键词
    var URL_keyword_evaindexinfo = "evaindexinfo"; //评价详细信息URL关键词
    var URL_keyword_resultlist = "resultlist"; //待评价列表URL关键词
    var URL_keyword_login = "login.jsp";
    var URL_keyword_homeindex = "homepage/index.do";
    var URL_keyword_indexnew = "academic/index_new.jsp";
    var URL_keyword_menu = "listLeft.do";

    var dataMap = new Map(); //数据map

    listen(); //程序开始

    /**
     * 监听程序开始,监听用户进入的页面是匹配
     */
    function listen() {
        console.log("Auto-Eva started...");

        if (window.location.href.indexOf(URL_keyword_menu) !== -1) {
            console.log("menu");
            appendController();
        }

        if (
            window.location.href.indexOf(URL_keyword_homeindex) !== -1 ||
            window.location.href.indexOf(URL_keyword_login) !== -1
        ) {
            //考虑到每次重新登录账号,清除过期的GPA_ALL和GPA_RC
            GM_deleteValue("GPA_ALL");
            GM_deleteValue("GPA_RC");
        }

        if (window.location.href.indexOf(URL_keyword_score) !== -1) {
            let query_table = $("body > center > form > table > tbody > tr");
            let cal_td =
                "<td><input name='hae_cal_score' type='button' id='hae_cal_score' class='button' value='计算GPA'></td>";
            query_table.append(cal_td); //添加按钮

            appendGPA();

            //点击计算GPA
            $("#hae_cal_score").click(function () {
                calGPA();
            });
        }

        if (window.location.href.indexOf(URL_keyword_evaindexinfo) !== -1) {
            eva_core();
        }
        if (window.location.href.indexOf(URL_keyword_resultlist) !== -1) {
            let eva_tag = $("#li14 > a");
            let count = 0;
            //let innerTabRow = $("body > center > table.infolist_tab > tbody > tr");

            //body > center > table.infolist_tab > tbody > tr:nth-child(1)
            let innerTabRow = $(
                "body > center > table.infolist_tab > tbody > tr"
            );
            // console.log(innerTabRow)
            for (let i = 1; i <= innerTabRow.length; i++) {
                //console.log(innerTabRow[i])
                //评估 a href标签  nth-child 选择第n个子节点,从1开始
                let rowLink = $(
                    "body > center > table.infolist_tab > tbody > tr:nth-child(" +
                        i +
                        ") > td:nth-child(4) > a"
                );
                let evaStatus = $(
                    "body > center > table.infolist_tab > tbody > tr:nth-child(" +
                        i +
                        ") > td:nth-child(3)"
                );
                // body > center > table.infolist_tab > tbody > tr:nth-child(8) > td:nth-child(4) > a
                if (
                    typeof evaStatus.html() != "undefined" &&
                    evaStatus.html().indexOf("未评估") !== -1 &&
                    rowLink.length !== 0
                ) {
                    //未评估 可以点:评估
                    //console.log(rowLink[0].href)
                    window.parent.frames["mainFrame"].location.href =
                        rowLink[0].href;
                }
                if (
                    typeof evaStatus.html() != "undefined" &&
                    evaStatus.html().indexOf("已评估") !== -1
                ) {
                    count++;
                }

                //body > table > tbody > tr:nth-child(8) > td:nth-child(4) > a
            }
            if (count >= innerTabRow.length - 1) {
                //如果完成了,则取消这个监听器
                console.log("Finish all " + count + " item(s).");
            } else {
                console.log(
                    "Already finish " +
                        count +
                        " item(s), remain " +
                        (innerTabRow.length - 1) +
                        ". "
                );
            }
        }
    }

    /**
     * menu 拼接进入自动评估选项
     */
    function appendController() {
        var gotoEva =
            '<li><script type="text/javascript">    var moduleStatus506 = true;</script></head><body><a onclick="showInfo(506)" href="./accessModule.do?moduleId=506" target="mainFrame"><span style=\'color: red\'>>>点击进入自动评估课程<<</span></a></li>';
        $("#menu").append(gotoEva);
    }

    /**
     * 计算当前页面的GPA
     * 注:其中多处计算扩大了倍数,是为了变成整数计算,保持浮点数精度
     * @returns {gpaCurrentPage:string,creditCurrentPage:string}
     */
    function calculateCurrentPage() {
        let gpaCurrentPage = 0; //当前页面
        let gpaRCCurrentPage = 0;
        let creditCurrentPage = 0; //当前页面
        let creditRCCurrentPage = 0;

        let rowlength = $("body > center > table > tbody > tr").length - 1;
        let headRow = $("body > center > table > tbody > tr:nth-child(1)");

        for (let i = 2; i <= 1 + rowlength; i++) {
            //当前行的所有td
            let currentRowTds = $(
                "body > center > table > tbody > tr:nth-child(" + i + ") > td"
            );

            let credit = currentRowTds[7].innerHTML.trim();
            let gpa = currentRowTds[13].innerHTML.trim();
            let testState = currentRowTds[11].innerHTML.trim(); //考试状态
            let courseType = currentRowTds[9].innerHTML.trim();
            if (testState !== "正常考试") {
                //补考或重修,不计入
                continue;
            }

            //gpa = gpa * 100
            gpa = parseFloat((gpa * 100).toFixed(10));
            //credit = credit * 10
            credit = parseFloat((credit * 10).toFixed(10));

            if (courseType === "必修") {
                gpaRCCurrentPage += gpa * credit;
                creditRCCurrentPage += credit;
            }
            gpaCurrentPage += gpa * credit;
            creditCurrentPage += credit;
        }

        //creditCurrentPage *= 100;
        creditCurrentPage = parseFloat((creditCurrentPage * 100).toFixed(10));
        creditRCCurrentPage = parseFloat(
            (creditRCCurrentPage * 100).toFixed(10)
        );
        return {
            gpaCurrentPage: gpaCurrentPage / creditCurrentPage,
            gpaRCCurrentPage: gpaRCCurrentPage / creditRCCurrentPage,
        };
    }
    function GPAToScore(GPA) {
        return (parseFloat(GPA) * 10 + 50).toFixed(2);
    }
    /**
     *  某一门学科的GPA:(期末总评-50.0)/10.0(不及格科目GPA为0)
     *  备注:五级分制期末总评:优秀95,良好85,中75,及格65,不及格50。
     *  所有学科的GPA:(学科1GPA×学科1学分数+学科2GPA×学科2学分数+......)]/(所有学科学分之和)
     *
     *  注:其中多处计算扩大了倍数,是为了变成整数计算,保持浮点数精度
     */
    function calGPA() {
        $("#hae_tb_gpa").remove();
        let gpaALL = GM_getValue("GPA_ALL");
        let gpaRC = GM_getValue("GPA_RC");
        let gpaCurrentPage = calculateCurrentPage().gpaCurrentPage;
        let gpaRCCurrentPage = calculateCurrentPage().gpaRCCurrentPage;
        if (typeof gpaALL == "undefined" || typeof gpaRC == "undefined") {
            if (dataMap.has(URL_keyword_score)) {
                // 已经获取过数据,仅仅点击关闭,所以下次不需要再次ajax请求
            } else {
                //请求全部科目
                $.ajax({
                    type: "POST",
                    url: "http://jwzx.hrbust.edu.cn/academic/manager/score/studentOwnScore.do",
                    data: {
                        year: "",
                        term: "",
                        prop: "",
                        groupName: "",
                        para: "0",
                        sortColumn: "",
                        Submit: "查询",
                    },
                    async: false,
                    success: function (result) {
                        var base = document.createElement("div"); //创建dom节点
                        base.innerHTML = result; //把请求的网页放到div中
                        //div > center > table > tbody > tr:nth-child(2)

                        let tbody = base.querySelector(
                            "div > center > table > tbody"
                        ); //选择器选择出tr
                        tbody.removeChild(tbody.firstChild); //移除表头
                        let rows = tbody.children; //rows为许多行成绩

                        dataMap.set(URL_keyword_score, rows);
                    },
                });
            }
            let rows = dataMap.get(URL_keyword_score);

            let gpaALLWeight = 0; //加权GPA:单科GPA*学分之和
            let creditALL = 0; //单科学分之和

            let gpaRCWeight = 0; //加权GPA:仅必修课
            let creditRC = 0; //仅必修课

            for (let i = 0; i < rows.length; i++) {
                let currentRowTds = rows[i].children;

                let score = currentRowTds[6].innerHTML.trim(); //成绩
                let credit = Number.parseFloat(
                    currentRowTds[7].innerHTML.trim()
                ); //学分
                let courseType = currentRowTds[9].innerHTML.trim(); //课程属性,是否为必修
                let passState = currentRowTds[12].innerHTML.trim(); //通过状态
                let testState = currentRowTds[11].innerHTML.trim(); //考试状态

                if (testState !== "正常考试") {
                    //补考或重修,不计入
                    continue;
                }

                //五级分制期末总评:优秀95,良好85,中75,及格65,不及格50。
                if (isNaN(Number(score))) {
                    //如果不是数字类型
                    score = getScoreBy5Level(score.trim());
                }

                let GPA = passState === "及格" ? (score - 50.0) / 10.0 : 0;

                //GPA = GPA * 100
                GPA = parseFloat((GPA * 100).toFixed(10));
                //credit = credit * 10
                credit = parseFloat((credit * 10).toFixed(10));

                if (GPA === 0) {
                    //挂科
                    gpaALLWeight += 0; //加权GPA + 0
                    creditALL += credit; //学分需要加
                    if (courseType === "必修") {
                        //必修课
                        gpaRCWeight += 0; //加权GPA + 0
                        creditRC += credit; //学分需要加
                    }
                } else {
                    //通过

                    gpaALLWeight += GPA * credit; //加权GPA + 0
                    creditALL += credit; //学分需要加
                    if (courseType === "必修") {
                        //必修课
                        gpaRCWeight += GPA * credit; //加权GPA 增加
                        creditRC += credit; //学分需要加
                    }
                }
            }

            //creditALL *= 100;
            creditALL = parseFloat((creditALL * 100).toFixed(10));
            //creditRC *= 100;
            creditRC = parseFloat((creditRC * 100).toFixed(10));

            gpaALL = gpaALLWeight / creditALL;
            gpaRC = gpaRCWeight / creditRC;
            GM_setValue("GPA_ALL", gpaALL);
            GM_setValue("GPA_RC", gpaRC);
        }

        let queryTable = $("body > center > form");
        let GPA_Table_HTML =
            "" +
            "<table class='form' cellspacing='0' cellpadding='0' id='hae_tb_gpa'>" +
            "<tr><td>所有已修学科GPA(全部:包括必修、限选、任选)</td><td>" +
            gpaALL.toFixed(2) +
            "(" +
            GPAToScore(gpaALL) +
            ")</td></tr>" +
            "<tr><td>所有已修学科GPA(仅必修)</td><td>" +
            gpaRC.toFixed(2) +
            "(" +
            GPAToScore(gpaRC) +
            ")</td></tr>" +
            "<tr><td>当前页面GPA(全部:包括必修、限选、任选)</td><td>" +
            gpaCurrentPage.toFixed(2) +
            "(" +
            GPAToScore(gpaCurrentPage) +
            ")</td></tr>" +
            "<tr><td>当前页面GPA(仅必修)</td><td>" +
            gpaRCCurrentPage.toFixed(2) +
            "(" +
            GPAToScore(gpaRCCurrentPage) +
            ")</td></tr>" +
            "<tr style='color: red'><td colspan='2' title='某一门学科的GPA:(期末总评-50.0)/10.0(不及格科目GPA为0)\n备注:五级分制期末总评:优秀95,良好85,中75,及格65,不及格50。\n所有学科的GPA:(学科1GPA×学科1学分数+学科2GPA×学科2学分数+......)]/(所有学科学分之和)'>提示:计算结果仅供参考!<b>【计算方法】</b>(鼠标悬浮查看)</td></tr>" +
            "<tr><td colspan='2' style='text-align: center'><input type='button' class='button' onclick=' $(\"#hae_tb_gpa\").remove();' value='关闭'></td></tr>" +
            "</table>";

        queryTable.append(GPA_Table_HTML);
    }

    /**
     * 核心评价代码,在评价页面,自动选择单选框,自动填写评语
     */
    function eva_core() {
        //取出所有radios组件
        var radios = $("input:radio");
        //console.log(radios)
        //有radios的行数
        let rowNums = radios.length % 4 === 0 ? radios.length / 4 : -1;
        console.log("radios评估共:" + rowNums + "行");

        if (rowNums !== -1) {
            let level2 = Math.ceil(Math.random() * (rowNums - 1));

            //console.log("rowNums:" + rowNums)
            switch (evaluation_level) {
                case 0:
                case 1: //优秀评价
                    //除了最后一行总评,随机取出一个,评价为良好,其他全部优秀
                    for (let i = 0; i < rowNums; i++) {
                        if (i !== level2) {
                            checkRadioIndexOf(radios, i, 0);
                        } else {
                            checkRadioIndexOf(radios, i, 1);
                        }
                    }
                    break;
                case 2: //中等评价
                    for (let i = 0; i < rowNums; i++) {
                        let rand0 = Math.ceil(Math.random() * (rowNums - 1));
                        if (i !== level2) {
                            checkRadioIndexOf(radios, i, (rand0 & 1) + 1);
                        } else {
                            checkRadioIndexOf(radios, i, 0);
                        }
                    }
                    break;
                case 3: //较差评价
                    for (let i = 0; i < rowNums; i++) {
                        let rand0 = Math.ceil(Math.random() * (rowNums - 1));
                        if (i !== level2) {
                            checkRadioIndexOf(radios, i, (rand0 & 1) + 2);
                        } else {
                            checkRadioIndexOf(radios, i, 0);
                        }
                    }
                    break;

                case 4: //全随机评价
                    //防止随机全重复,无法提交
                    checkRadioIndexOf(radios, 0, 0);
                    checkRadioIndexOf(radios, 1, 1);
                    for (let i = 2; i < rowNums; i++) {
                        let rand0 = Math.ceil(Math.random() * (rowNums - 1));
                        checkRadioIndexOf(radios, i, rand0 & 0b11);
                    }
                    break;
                default: //默认优秀评价
                    for (let i = 0; i < rowNums; i++) {
                        if (i !== level2) {
                            checkRadioIndexOf(radios, i, 0);
                        } else {
                            checkRadioIndexOf(radios, i, 1);
                        }
                    }
                    break;
            }
        }

        //填充文字评价
        let textareas = $("textarea");

        textareas[0].innerText = randomApprove();
        textareas[1].innerText = randomImprove();
        textareas[2].innerText = randomAdvice();
        textareas[3].innerText = randomOther();

        //提交
        if (auto_commit === 1 && evaluation_level === 0) {
            //document.form1.sub_b.disabled=true;
            var cn = 0;

            for (let i = 0; i < document.form1.elements.length; i++) {
                if (document.form1.elements[i].type === "radio") {
                    if (document.form1.elements[i].checked) {
                        cn++;
                    }
                }
            }
            //alert(cn)
            setTimeout(function () {
                document.form1.submit();
            }, sleep_time);
            //document.form1.submit();
        }
    }

    //选择第row行的第index个radio,其中row从0开始,index从0开始
    function checkRadioIndexOf(radios, row, index) {
        radios[row * 4 + index].setAttribute("checked", true);
    }

    //你认为教师在教学上最值得肯定之处? 随机一个 老师值得肯定的地方
    function randomApprove() {
        //str中可以自定义随机评价老师的内容
        let str = text_approve;

        let rand = Math.ceil(Math.random() * str.length) - 1;
        if (rand < 0) {
            rand = 0;
        }
        let good = str[rand];
        return good;
    }

    //你认为教师在教学上最应该改进之处? 随机一个 应该改进的地方
    function randomImprove() {
        //str中可以自定义随机还需提升的部分
        let str = text_improve;

        let rand = Math.ceil(Math.random() * str.length) - 1;
        if (rand < 0) {
            rand = 0;
        }
        let good = str[rand];
        return good;
    }

    //你对本门课程选用教材及参考书有何意见和建议? 随机教材建议
    function randomAdvice() {
        //str中可以自定义随机还需提升的部分
        let str = text_advice;

        let rand = Math.ceil(Math.random() * str.length) - 1;
        if (rand < 0) {
            rand = 0;
        }
        let good = str[rand];
        return good;
    }

    //其他建议
    function randomOther() {
        //str中可以自定义随机还需提升的部分
        let str = text_other;

        let rand = Math.ceil(Math.random() * str.length) - 1;
        if (rand < 0) {
            rand = 0;
        }
        let good = str[rand];
        return good;
    }

    /**
     * 在个人成绩页面,自动拼接单科GPA到每一行末尾
     */
    function appendGPA() {
        let rowlength = $("body > center > table > tbody > tr").length - 1;
        let headRow = $("body > center > table > tbody > tr:nth-child(1)");
        //console.log(headRow)
        headRow.append("<th>单科GPA</th>");
        for (let i = 2; i <= 1 + rowlength; i++) {
            let currentRow = $(
                "body > center > table > tbody > tr:nth-child(" + i + ")"
            );

            //当前行的所有td
            let currentRowTds = $(
                "body > center > table > tbody > tr:nth-child(" + i + ") > td"
            );

            // console.log(tdInfos)
            let score = currentRowTds[6].innerHTML.trim();
            let credit = currentRowTds[7].innerHTML.trim();
            let pass_state = currentRowTds[12].innerHTML.trim();
            //五级分制期末总评:优秀95,良好85,中75,及格65,不及格50。
            if (isNaN(Number(score))) {
                //如果不是数字类型
                score = getScoreBy5Level(score.trim());
            }

            let GPA = pass_state === "及格" ? (score - 50.0) / 10.0 : 0;

            let td_GPA = "<td>" + GPA + "</td>";
            currentRow.append(td_GPA);
        }
    }

    //五级分制期末总评:优秀95,良好85,中75,及格65,不及格50。
    function getScoreBy5Level(str) {
        let score = NaN;
        switch (str.trim()) {
            // score.trim()
            case "优秀":
                score = 95;
                break;
            case "良好":
                score = 85;
                break;
            case "中":
                score = 75;
                break;
            case "及格":
                score = 65;
                break;
            case "不及格":
                score = 50;
                break;
            default:
                score = str;
        }
        return score;
    }
})();