您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculate GPA in URP system
// ==UserScript== // @name BUPT GPA // @namespace https://ssine.cc/ // @version 3.5 // @description Calculate GPA in URP system // @author Liu Siyao // @include *://jwxt.bupt.edu.cn/jwLoginAction.do // @include *://jwxt.bupt.edu.cn/caslogin.jsp // @include *://vpn.bupt.edu.cn/http/jwxt.bupt.edu.cn/jwLoginAction.do // @include *://vpn.bupt.edu.cn/https/jwxt.bupt.edu.cn/jwLoginAction.do // @include *://jwgl.bupt.edu.cn/jsxsd/framework/xsMain.jsp // @include *://vpn.bupt.edu.cn/http/jwgl.bupt.edu.cn/jsxsd/framework/xsMain.jsp // @include *://vpn.bupt.edu.cn/https/jwgl.bupt.edu.cn/jsxsd/framework/xsMain.jsp // @include *://webvpn.bupt.edu.cn/*/jsxsd/framework/xsMain.jsp // @grant none // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js // @license GNU GPLv3 // ==/UserScript== (function () { 'use strict'; // set this true when GPA button disappears // brutal fix for the damn jwgl bug const iterate_query = true; function translate_sem_to_number(sem_str) { let sem_arr = sem_str.split('-'); return parseInt(sem_arr[1] + sem_arr[0] + sem_arr[2]); } // only work when iterate_query is true const sem_start = translate_sem_to_number('2019-2020-1'); // your first semester here let date = new Date(); let year = date.getFullYear(); // auto calculate the current semester const sem_end = translate_sem_to_number(year + '-' + (year + 1) + '-5'); const is_old_system = /jwxt/.test(window.location.href); async function run() { let promises = []; if (is_old_system) { promises = promises.concat([ $.get('/gradeLnAllAction.do?type=ln&oper=qbinfo'), $.get('/gradeLnAllAction.do?type=ln&oper=lnFajhKcCjInfo&lnxndm=*') ]); } else { if (iterate_query) { await $.get('/jsxsd/kscj/cjcx_query').then((data) => { let parser = new DOMParser(); let doc = parser.parseFromString(data, 'text/html'); let sem_options = doc.querySelectorAll("#kksj > option"); for (let i = 0; i < sem_options.length; i++) { let sem_str = sem_options[i].value; let sem_number = 0; sem_number = translate_sem_to_number(sem_str); if (isNaN(sem_number)) continue; if (sem_number > sem_end) continue; if (sem_number >= sem_start) { promises.push( $.post('/jsxsd/kscj/cjcx_list', { kksj: sem_str, kcxz: "", kcmc: "", xsfs: "all" })); } else { break; } }; }); } else { promises = promises.concat([ $.post('/jsxsd/kscj/cjcx_list', { kksj: "", kcxz: "", kcmc: "", xsfs: "all" }), ]); } } Promise.all(promises).then((data) => { let algoNames = ['北邮官方', '标准4.0', '改进4.0', '北大4.0', '加拿大4.3', '中科大4.3', '上海交大4.3']; let algoArea = [ [59, 60, 60.5, 61, 61.5, 62, 62.5, 63, 63.5, 64, 64.5, 65, 65.5, 66, 66.5, 67, 67.5, 68, 68.5, 69, 69.5, 70, 70.5, 71, 71.5, 72, 72.5, 73, 73.5, 74, 74.5, 75, 75.5, 76, 76.5, 77, 77.5, 78, 78.5, 79, 79.5, 80, 80.5, 81, 81.5, 82, 82.5, 83, 83.5, 84, 84.5, 85, 85.5, 86, 86.5, 87, 87.5, 88, 88.5, 89, 89.5, 90, 90.5, 91, 91.5, 92, 92.5, 93, 93.5, 94, 94.5, 95, 95.5, 96, 96.5, 97, 97.5, 98, 98.5, 99, 99.5, 100], [59, 69, 79, 89, 100], [59, 69, 84, 100], [59, 63, 67, 71, 74, 77, 81, 84, 89, 100], [59, 64, 69, 74, 79, 84, 89, 100], [59, 60, 63, 64, 67, 71, 74, 77, 81, 84, 89, 94, 100], [59, 61, 64, 66, 69, 74, 79, 84, 89, 94, 100] ]; let algoGp = [ [0, 1.00, 1.07, 1.15, 1.22, 1.29, 1.36, 1.43, 1.50, 1.57, 1.64, 1.70, 1.77, 1.83, 1.90, 1.96, 2.02, 2.08, 2.14, 2.20, 2.26, 2.31, 2.37, 2.42, 2.48, 2.53, 2.58, 2.63, 2.68, 2.73, 2.78, 2.83, 2.87, 2.92, 2.96, 3.01, 3.05, 3.09, 3.13, 3.17, 3.21, 3.25, 3.29, 3.32, 3.36, 3.39, 3.43, 3.46, 3.49, 3.52, 3.55, 3.58, 3.61, 3.63, 3.66, 3.68, 3.71, 3.73, 3.75, 3.77, 3.79, 3.81, 3.83, 3.85, 3.86, 3.88, 3.89, 3.91, 3.92, 3.93, 3.94, 3.95, 3.96, 3.97, 3.98, 3.98, 3.99, 3.99, 4.00, 4.00, 4.00, 4.00], [0, 1, 2, 3, 4], [0, 2, 3, 4], [0, 1, 1.5, 2, 2.3, 2.7, 3, 3.3, 3.7, 4], [0, 2.3, 2.7, 3, 3.3, 3.7, 4, 4.3], [0, 1, 1.3, 1.5, 1.7, 2, 2.3, 2.7, 3, 3.3, 3.7, 4, 4.3], [0, 1, 1.7, 2, 2.3, 2.7, 3, 3.3, 3.7, 4, 4.3] ]; function getGP(score, i) { let area = algoArea[i]; let gp = algoGp[i]; for (let idx in area) { if (score <= area[idx]) return gp[idx]; } return score; }; class course { constructor(no, name, semester, type, credit, grade) { this.no = no; this.name = name; this.semester = semester; this.type = type; this.credit = credit; this.grade = grade; } } let calc_mat = []; let course_lst = []; let course_lst_csv = 'Name,Credit,Grade\n' let semesters = []; let course_types = ['必修', '选修', '任选']; let semester_name = ''; function fakeClick(obj) { let ev = document.createEvent("MouseEvents"); ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); obj.dispatchEvent(ev); } function exportRaw() { let urlObject = window.URL || window.webkitURL || window; let export_blob = new Blob([course_lst_csv]); let save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a") save_link.href = urlObject.createObjectURL(export_blob); save_link.download = "my_grade.csv"; fakeClick(save_link); } function showResult() { // show courses in course_lst to div let sum = 0, total_credit = 0; let gpLst = [0, 0, 0, 0, 0, 0]; let used_couse_num = 0; for (let idx = 0; idx < course_lst.length; idx++) { let course = course_lst[idx]; if (!calc_mat[semesters.indexOf(course.semester)][course_types.indexOf(course.type)]) continue; total_credit += course.credit; sum += course.credit * course.grade; for (let j in gpLst) { gpLst[j] += course.credit * getGP(course.grade, j); } used_couse_num++; }; $('#gpa-res').empty(); $('#gpa-res').append($('<table>\ <tr><th>算法</th><th>GPA</th></tr>\ </table>')); for (let idx in gpLst) { let newTr = "<tr><td>" + algoNames[idx] + "</td><td>" + (gpLst[idx] / total_credit).toFixed(2) + "</td></tr>"; $('#gpa-res table').append($(newTr)); } let contentStr = "特殊加权学分绩: " + (sum / total_credit).toFixed(2); contentStr += "<br>已修读学分: " + total_credit.toString(); contentStr += "<br>计算的课程数: " + used_couse_num; contentStr += "<br>总课程数: " + course_lst.length; $('#gpa-res').append($('<p>' + contentStr + '</p>')); } let parser = new DOMParser(); if (is_old_system) { // prepare fallback grades when normal grade is one of 优良中差 let course_no_to_grade = {}; parser.parseFromString(data[1], "text/html").querySelectorAll('.odd').forEach((row) => { if (row.childNodes.length == 11) { let course_no = row.childNodes[1].innerText.trim(); let grade = parseFloat(row.childNodes[7].innerText.trim()); if (course_no && grade) course_no_to_grade[course_no] = grade; } }); // parse grades let body_lst = parser.parseFromString(data[0], "text/html").getElementsByTagName('body')[0].childNodes; for (let i = 0; i < body_lst.length; i++) { if (body_lst[i].tagName == 'A') { semester_name = body_lst[i].name; if (semesters.indexOf(semester_name) == -1) { semesters.push(semester_name); } } else if (body_lst[i].className == 'titleTop2') { let entry = $(body_lst[i]).find('.odd'); for (let j = 0; j < entry.length; j++) { let lst = entry[j].getElementsByTagName('td'); let grade_text = entry[j].getElementsByTagName('p')[0].innerText.trim(); let grade = parseFloat(grade_text); if (grade_text in ['优', '良', '中', '差']) grade = course_no_to_grade[lst[0].innerText.trim()]; if (isNaN(grade)) continue; let course_no = lst[0].innerText.trim(); let course_name_zh = lst[2].innerText.trim(); let course_name_en = lst[3].innerText.trim(); let course_type = lst[5].innerText.trim(); let course_credit = lst[4].innerText.trim(); course_lst.push(new course( course_no, course_name_zh, semester_name, course_type, parseFloat(course_credit), grade )); course_lst_csv += (course_name_en + ',' + course_credit + ',' + grade + '\n'); } } } } else { for (let i = 0; i < data.length; i++) { let named_grade = { '差': 65, '及格': 65, '合格': 65, '中': 75, '良': 85, '优': 95 }; // parse grades let body_lst = parser.parseFromString(data[i], "text/html").querySelector('#dataList tbody').childNodes; body_lst = Array.prototype.slice.call(body_lst, 0).filter((_, idx) => idx % 2 === 0).slice(1); body_lst = body_lst.map(it => it.cells); try { for (let item of body_lst) { if (item[6].innerText.indexOf('免修') !== -1) continue; if (item[6].innerText.indexOf('缓考') !== -1) continue; semester_name = item[1].innerText.trim(); if (semesters.indexOf(semester_name) == -1) { semesters.push(semester_name); } let grade_text = item[5].innerText.trim(); let grade = parseFloat(grade_text); if (grade_text in named_grade) grade = named_grade[grade_text]; if (isNaN(grade)) continue; let course_no = item[2].innerText.trim(); let course_name_zh = item[3].innerText.trim(); let course_name_en = item[3].innerText.trim(); // not found yet... let course_type = item[12].innerText.trim(); if (course_type === '公选') course_type = '任选'; let course_credit = item[7].innerText.trim(); course_lst.push(new course( course_no, course_name_zh, semester_name, course_type, parseFloat(course_credit), grade )); course_lst_csv += (course_name_en + ',' + course_credit + ',' + grade + '\n'); } } catch (e) { continue; } } } for (let i = 0; i < semesters.length; i++) calc_mat.push([true, true, false]); // vue & ui stuff let gpa_div = $(`<div id="gpa"> <div id="gpa-side"> <div id="gpa-modify"> <h2>课程属性:</h2> <table> <tr> <th>课程名</th> <th>类型</th> <th>成绩</th> <th>学分</th> </tr> <tr v-for="c in courses"> <td>{{c.name}}</td> <td> <select v-model="c.type"> <option>必修</option> <option>选修</option> <option>任选</option> </select> </td> <td>{{c.grade}}</td> <td>{{c.credit}}</td> </tr> </table></div> </div> <div id="gpa-main-frame"> <div id="calc-app"> <h2>要计算的课程:</h2> <table> <tr> <th>学期</th> <th>必修</th> <th>选修</th> <th>任选</th> </tr> <tr v-for="(r, idx) in mat"> <td>{{ semesters[idx] }}</td> <td><input type="checkbox" id="checkbox" v-model="r[0]"></td> <td><input type="checkbox" id="checkbox" v-model="r[1]"></td> <td><input type="checkbox" id="checkbox" v-model="r[2]"></td> </tr> </table></div> <h2>结果:</h2> <div id="gpa-res"> </div> <div id="csv-download"> </div> <hr> <p>程序完全基于前端,不会存储个人信息。</p> <p>使用过程中有问题请在<a target="_blank" href="https://github.com/ssine/BUPT-GPA">代码仓库</a>提 issue</p> <p>没问题也欢迎来点个 star ヽ(✿゚▽゚)ノ</p> <p>欢迎把<a target="_blank" href="https://gf.qytechs.cn/zh-CN/scripts/369550-bupt-gpa">这个脚本</a>分享给你的朋友哦(*/ω\*)</p> </div> </div>`); let sheet_css = $(`<style> #gpa { position: absolute; right: 70px; bottom: 20px; height: 80%; background-color: rgba(255,255,255,0.9); font-size: 16px; line-height: 23px; } #gpa table { border-collapse: separate; border-spacing: 15px 0; } #gpa-side { float: left; margin-right: 20px; height: 100%; overflow: auto; } #gpa-main-frame { float: left; height: 100%; overflow: auto; } #gpa-modify table tr td:first-child, #gpa-modify table tr th:first-child { width: 200px; } #calc-app { } #res-app { margin-top: 50px; } #gpa-btn { position: absolute; right: 20px; bottom: 20px; background-color: RGB(119,119,119); color: rgb(255,255,255); height: 50px; width: 50px; border-radius: 50px; font-family: sans-serif; } </style>`); let btn_download = $('<button id="download-btn">下载CSV成绩单</button>'); btn_download.click(() => { exportRaw(); }); $('head').append(sheet_css); gpa_div.hide(); $('html').append(gpa_div); let btn = $('<button id="gpa-btn">GPA</button>'); btn.click(() => { let app = $('#gpa'); if (app.css('display') == 'none') app.css('display', ''); else app.css('display', 'none'); }); $('html').append(btn); $('#csv-download').append(btn_download); showResult(); const gpa_modify = new Vue({ el: '#gpa-modify', data: { courses: course_lst }, watch: { courses: { handler(newValue, oldValue) { showResult(); }, deep: true } } }); let calc_app = new Vue({ el: '#calc-app', data: { mat: calc_mat, semesters: semesters }, watch: { mat: showResult } }); }) } if (is_old_system) window.parent.frames[1].onload = run; else window.onload = run; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址