// ==UserScript== // @name 金智教育教务成绩导出和加权平均学分计算(如南大、南师大、复旦) // @namespace hb123 // @version 0.2 // @description 导出全部成绩,金智教育教务可能可以,如复旦、南大、南师大 // @author 何碧 // @match *://*.edu.cn/* // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.core.min.js // @grant GM_addStyle // @grant GM_getValue // @grant GM_listValues // @grant GM_setValue // @grant GM_addValueChangeListener // ==/UserScript== /* ==UserConfig== configure: terms: title: 选择查询哪些学期 type: mult-select description: 选择查询的学期有哪些 bind: $terms all: title: 查询所有 type: checkbox description: 选中即查询所有学期 default: false mode: title: 模式 description: 计算 or 导出 type: select default: "导出为xlsx" values: ["计算","导出为xlsx"] exclude: title: 筛选关键词(用,或者, 分割) type: text description: 计算过程中需要排除的关键词 include: title: 包含的关键词(用,或者, 分割) type: text description: 计算过程中需要包含的关键词,只要含有其中的关键词,一定不会被过滤 ==/UserConfig== */ // setInterval(() => { // console.log(GM_listValues()) // console.log(GM_getValue("configure.terms"), GM_getValue("configure.all"), GM_getValue("terms")) // }, 2000); (function () { 'use strict'; let now = new Date().getFullYear() let terms = []; for (let index = 0; index < 4; index++) { terms.push(`${now - index}-${now - index + 1}-1`, `${now - index}-${now - index + 1}-2`) } GM_setValue("terms", terms) make_xlsx_lib(XLSX) waitUtil('ul.jqx-tabs-title-container', function (target) { let box = document.createElement("div"); box.style = "display:flex;float:right;margin-right:20px;justify-content: center;align-items: center;flex: 1;height: 100%;" target.appendChild(box); box.innerHTML = ` `; let btn = document.createElement("button"); btn.textContent = GM_getValue("configure.mode"); btn.className = "jw-btn"; btn.style = ` outline: none; border: #ccc 1px solid; border-radius: 8px; padding: 2px 8px; background-color: #1195da; box-shadow: 0px 0px 1px 0.5px #119ddd; `; btn.id = "jw-export"; btn.onclick = () => { if (GM_getValue("configure.all") && GM_getValue("configure.mode", "") == "导出为xlsx") { console.log("[何碧]正在导出......", XLSX); getAllCourses().then(courses => { exportXlsx(courses) }) } else if (!GM_getValue("configure.all") && GM_getValue("configure.mode", "") == "计算") { let terms = GM_getValue("configure.terms", []) getCourses(terms).then(courses => { let average = calulate(courses) document.getElementById("average-credit").textContent = `${average.toFixed(2)}分, GPA:${(((average) / 100) * 4).toFixed(3)}` }) } else if (GM_getValue("configure.all") && GM_getValue("configure.mode", "") == "计算") { getAllCourses().then(courses => { let average = calulate(courses).toFixed(2); document.getElementById("average-credit").textContent = `${average}分, GPA:${(((average) / 100) * 4).toFixed(3)}`; }) } else { let terms = GM_getValue("configure.terms", []) getCourses(terms).then(courses => { exportXlsx(courses) }) } } box.appendChild(btn); GM_addValueChangeListener("configure.mode", () => { document.getElementById("jw-export").textContent = GM_getValue("configure.mode", "计算") }) }, 5000); GM_addStyle(` .jw-btn:hover{ box-shadow: 2px 2px 4px 0.5px #119ddd; } `); })(); function waitUtil(ele, callback, timeout) { let success = false; let id = setInterval(function () { let target = document.querySelector(ele) if (target != null) { success = true clearInterval(id); callback(target) } }, 100) setTimeout(() => { if (!success) { clearInterval(id) console.log("[何碧]页面超时") } }, timeout) } function getCourses(terms) { let querySetting = [{ "name": "XNXQDM", "value": terms.join(","), "linkOpt": "and", "builder": "m_value_equal" }, { "name": "SFYX", "caption": "是否有效", "linkOpt": "AND", "builderList": "cbl_m_List", "builder": "m_value_equal", "value": "1", "value_display": "是" }] let form = `querySetting=${JSON.stringify(querySetting)}&*order=${encodeURIComponent("KCH,KXH")}&pageSize: 10&pageNumber: 1` return fetch("/jwapp/sys/cjcx/modules/cjcx/xscjcx.do", { "headers": { "accept": "application/json, text/javascript, */*; q=0.01", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest" }, "referrerPolicy": "strict-origin-when-cross-origin", "body": form, "method": "POST", "mode": "cors", "credentials": "include" }).then(response => response.json()).then(result => { return result.datas.xscjcx.rows }) } function getAllCourses() { return fetch("/jwapp/sys/cjcx/modules/cjcx/xscjcx.do", { "headers": { "accept": "application/json, text/javascript, */*; q=0.01", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest" }, "referrerPolicy": "strict-origin-when-cross-origin", "body": "querySetting=%5B%7B%22name%22%3A%22SFYX%22%2C%22caption%22%3A%22%E6%98%AF%E5%90%A6%E6%9C%89%E6%95%88%22%2C%22linkOpt%22%3A%22AND%22%2C%22builderList%22%3A%22cbl_m_List%22%2C%22builder%22%3A%22m_value_equal%22%2C%22value%22%3A%221%22%2C%22value_display%22%3A%22%E6%98%AF%22%7D%2C%7B%22name%22%3A%22SHOWMAXCJ%22%2C%22caption%22%3A%22%E6%98%BE%E7%A4%BA%E6%9C%80%E9%AB%98%E6%88%90%E7%BB%A9%22%2C%22linkOpt%22%3A%22AND%22%2C%22builderList%22%3A%22cbl_String%22%2C%22builder%22%3A%22equal%22%2C%22value%22%3A0%2C%22value_display%22%3A%22%E5%90%A6%22%7D%5D&*order=-XNXQDM%2C-KCH%2C-KXH&pageSize=100&pageNumber=1", "method": "POST", "mode": "cors", "credentials": "include" }).then(response => response.json()).then(result => { return result.datas.xscjcx.rows }) } const exportXlsx = (courses) => { const wb = XLSX.utils.book_new(); let table = [["课程名称", "学年学期", "课序号", "学分", "成绩", "满分", "学时", "修读方式", "修读类型", "重修初修", "课程性质", "考试日期", "开课单位", "是否及格"]] courses.forEach(course => { table.push([course.KCM, course.XNXQDM_DISPLAY, course.XSKCH, parseInt(course.XF),// 学分 course.ZCJ,// 成绩 parseInt(course.DJCJLXDM),// 满分 course.XS, //学时 course.XDFSDM_DISPLAY,//修读方式 course.SFZX_DISPLAY, // 修读类型 course.CXCKDM_DISPLAY, course.KCLBDM_DISPLAY, // 课程性质 course.XNXQDM_DISPLAY, // 考试日期 course.KKDWDM_DISPLAY, course.SFJG_DISPLAY]) }) const ws = XLSX.utils.aoa_to_sheet(table); XLSX.utils.book_append_sheet(wb, ws, "所有成绩"); XLSX.writeFile(wb, "所有成绩.xlsx"); } function calulate(courses) { courses = CoursesFilter(courses) let res = courses.reduce((prev, curr) => { prev.credits += parseInt(curr.XF) prev.total += parseFloat(curr.ZCJ * curr.XF) return prev; }, { credits: 0, total: 0 }) console.log(`平均学分:${res.total / res.credits}`) return res.total / res.credits } function CoursesFilter(courses) { let inclueStr = GM_getValue("configure.include", "") let includes = inclueStr.split(/[,,]/g) let exclueStr = GM_getValue("configure.exclude", "") let excludes = exclueStr.split(/[,,]/g) console.log("包含", includes, "排除", excludes) return courses.filter(course => { let str = JSON.stringify(course); includes.forEach(v => { if (str.indexOf(v) == -1) { console.log(`过滤了${course.KCM}`) return false } else { return true } }) excludes.forEach(v => { if (str.indexOf(v) != -1) { console.log(`过滤了${course.KCM}`) return false } }) return true }) }