用于分析杭电信工教务的详细成绩并展示
// ==UserScript== // @name 杭电信工教务成绩分析 // @namespace https://chiyukiruon.com // @version 1.2.0 // @description 用于分析杭电信工教务的详细成绩并展示 // @author ChiyukiRuon // @source https://chiyukiruon.com // @license GNU General Public License v3 // @supportURL https://github.com/ChiyukiRuon/hziee-score-detail-js/issues // @match http://10.130.5.235:6379/* // @match http://10.130.5.236/* // @match http://10.130.5.229:6379/* // @icon data:image/png;base64,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###WYjkCC2RsuDEkgQFGeP2QgkiK3R8qAEEgTF2WM2Aglia7Q8KIEEQXH2mI1AgtgaLQ9KIEFQnD1mI5AgtkbLgxJIEBRnj9kIJIit0fKgBBIExdljNgIJYmu0PCiBBEFx9piNQILYGi0PSiBBUJw9ZiOQILZGy4MSSBAUZ4/ZCCSIrdHyoAQSBMXZYzYCCWJrtDwogQRBcfaYjUCC2BotD0ogQV###WYjkCC2RsuDEkgQFGeP2QgkiK3R8qAE/gOZdSLYPv61iwAAAABJRU5ErkJggg== // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @grant GM_addValueChangeListener // @grant GM_xmlhttpRequest // @grant GM_download // @run-at document-start // ==/UserScript== (function() { 'use strict' const VERSION = '1.2.0' // 版本号 let ping // 定时器 let enablePing = GM_getValue('enablePing', false) clearInterval(ping) if (enablePing) keep###Connection() // 检查是否开启###保活 // 检查浏览器是否支持通知 if (!("Notification" in window)) { console.info("此浏览器不支持通知") } else { Notification.requestPermission().then((permission) => { if (permission === 'default') { console.info('未设置通知权限') } }); } // 注册全局样式 let css = `table {border-collapse: collapse;width: 100%;} table, th, td {border-bottom: 1px solid rgb(236, 238, 244);} table tr:nth-child(even) {background-color: rgb(250, 250, 250);} table tr:nth-child(odd) {background-color: rgb(255, 255, 255);} .main-table:hover {background-color: rgb(245, 247, 250)} .table-text {margin-left: 10px} .btn-group {background-color: #FFFFFF; color: #303133; border: 1px solid #DCDFE6; border-radius: 5px; padding: 5px 10px; margin: 5px 10px; transition-duration: 0.4s} .btn-group:hover {color: #47A2FF; border: 1px solid #47A2FF; cursor: pointer; transition-duration: 0.4s} .copyright {color: #303133; margin-top: 3px; a {color: #303133;} a:hover {color: #337ecc;}} .info-group {display: flex; align-items: center; margin-right: auto; font-size: 15px; color: #606266;}` GM_addStyle(css) /** * ###登录保活 * * @return void * @author ChiyukiRuon * */ function keep###Connection() { ping = setInterval(() => { GM_xmlhttpRequest({ method: "POST", url: window.location.href, headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" }, data:"content=erwer", onload: function(response){ // console.log("请求成功") }, onerror: function(response){ // console.log("请求失败") } }) // console.log(ping, 'running') }, 30000) } /** * 切换原始表格和详细成绩表格 * * @return void * @author ChiyukiRuon * */ function switchTable() { const scoreDetail = document.getElementById('score-detail') const noDataString = document.getElementById('no-data') if (scoreDetail) scoreDetail.style.display = scoreDetail.style.display === 'none'?'':'none' if (noDataString) noDataString.style.display = noDataString.style.display === 'none'?'':'none' originTable.style.display = originTable.style.display === 'none'?'':'none' originData.style.display = originData.style.display === 'none'?'':'none' } function isCredits(num) { const regex = /^(\d|\d\.\d|\.\d|\d\.)$/ return num.match(regex) !== null && num !== 0 } function isFinalScore(list, index) { const keywords = ['不及格', '不合格', '合格', '及格', '良好', '中等', '优秀',] const nextIndex = index + 1 const currentElement = list[index] const nextElement = list[nextIndex] if (!nextElement) { if (/^\d{2}$/.test(currentElement)) { return true } return keywords.includes(currentElement) } return !!(currentElement !== '是' && (nextElement === '是' || /(学院|学部)/.test(nextElement))) } function isPass(score) { if (/^\d{2}$/.test(score)) { return Number(score) >= 60 }else { const keywords = ['合格', '及格', '良好', '中等', '优秀',] return keywords.includes(score) } } function passNum(list) { let num = 0 for (let i in list) { if (list[i].isPass) { num++ } } return num } function base64ToString(base64String) { let normalString = '' if (base64String === '') { return } try { normalString = new TextDecoder().decode(Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0))) } catch (e) { return } return normalString } function siftString(string) { const regex = /[\u4e00-\u9fa5a-zA-Z0-9\-()().+\s]+(?=dd)/g; const r###lt = string.match(regex).map(str => str.replace(/dd$/, '')); splitCourse(r###lt) return splitCourse(r###lt) } function splitCourse(courseList) { const regex = /([\d-]+)-[a-zA-Z0-9-]+-\d+/; const r###lt = []; let currentSection = []; for (let i = 0; i < courseList.length; i++) { const item = courseList[i]; const match = item.match(regex); if (match) { if (currentSection.length > 0) { r###lt.push(currentSection); currentSection = []; } } currentSection.push(item); } if (currentSection.length > 0) { r###lt.push(currentSection); } return formatR###ltData(r###lt); } function formatR###ltData(courseList) { const r###ltList = [] for (let i = 1;i < courseList.length;i++) { const item = {} const assessmentScore = [] for (let j = 0;j < courseList[i].length;j++) { let courseListItem = courseList[i] if (j === 0) { item.courseId = courseListItem[j] } if (j === 4) { item.courseName = courseListItem[j].trimStart().replace(/^\d/g, "") } if (isCredits(courseListItem[j]) && j > 4) { item.credits = courseListItem[j].trimStart() } if (j > 6 && courseListItem[j] !== 'd' && !isCredits(courseListItem[j])) { if (isFinalScore(courseListItem, j)) { item.finalScore = courseListItem[j].trimStart() item.isPass = isPass(courseListItem[j]) }else if (isCredits(courseListItem[j-1])) { item.performanceScore = courseListItem[j].trimStart() }else { if (courseListItem[j] === '是') { item.isRenovate = courseListItem[j] === '是' }else if (courseListItem[j].match(/(学院|学部)/)) { item.college = courseListItem[j].trimStart() }else { assessmentScore.push(courseListItem[j].trimStart()) } } } } item.assessmentScore = assessmentScore r###ltList.push(item) } return r###ltList } /** * 计算学分 * * @param {Array} list 成绩列表 * @return {string} * @author ChiyukiRuon * */ function calcGP(list) { let totalCredits = 0 let totalGrade = 0 for (let i = 0;i < list.length; i++) { if (list[i].courseId.split('-')[3].charAt(0) !== 'R' && isPass(list[i].finalScore)) { let grade = stringGrade2Num(list[i].finalScore) totalCredits += parseFloat(list[i].credits) totalGrade += parseFloat(list[i].credits)*grade } } return (totalGrade/totalCredits/20).toFixed(2) } /** * 文字的成绩转化为数字 * * @param {String} grade 成绩 * @return {Number} * @author ChiyukiRuon * */ function stringGrade2Num(grade) { if (/^\d{2}$/.test(grade)) return parseFloat(grade) switch (grade) { case '优秀': return 100 case '良好': return 80 case '中等': return 60 case '合格': return 40 case '及格': return 20 default: return 0 } } /** * 创建表格 * * @param {Array} list * @return {String} 文本类型的HTML * @author ChiyukiRuon * */ function createTableValue(list) { let tableText = list.length === 0?`<div id="no-data" style="color: #E6A23C; font-size: medium; font-weight: bold">暂无数据</div>`:'' for (let i = 0;i < list.length;i++) { let assessmentScore = '' for(let j = 0;j < list[i].assessmentScore.length;j++) { assessmentScore += `${list[i].assessmentScore[j]} ` } if (list[i].isPass) { tableText += `<tr class="main-table" style="color: rgb(96, 98, 102); height: 40px"><td style="font-weight: bold;"><div class="table-text">${list[i].courseName}</div></td><td style="color: rgb(126, 192, 80)"><div class="table-text">${list[i].finalScore}</div></td><td><div class="table-text">${list[i].performanceScore}</div></td><td><div class="table-text">${assessmentScore}</div></td><td><div class="table-text">${list[i].credits}</div></td><td><div class="table-text">${list[i].college}</div></td></tr>` }else { tableText += `<tr class="main-table" style="color: rgb(96, 98, 102); height: 40px"><td style="font-weight: bold;"><div class="table-text">${list[i].courseName}</div></td><td style="color: rgb(228, 116, 112)"><div class="table-text">${list[i].finalScore}</div></td><td><div class="table-text">${list[i].performanceScore}</div></td><td><div class="table-text">${assessmentScore}</div></td><td><div class="table-text">${list[i].credits}</div></td><td><div class="table-text">${list[i].college}</div></td></tr>` } } return tableText.replace(/undefined/g, ' ') } // 隐藏原始表格 const originTable = document.getElementById('tbXsxx') const originData = document.getElementById('DataGrid1') if (originTable) { originTable.style.display = 'none' } if (originData) { originData.style.display = 'none' } let normalString = base64ToString(document.getElementById('__VIEWSTATE').value) let r###ltList = siftString(normalString) let targetElement = document.querySelector('#tbXsxx') if (targetElement) { // 版权及版本信息 console.log("\n _____ _ _ _ _ \n" + " / ____| | (_) | | (_)\n" + " | | | |__ _ _ _ _ _| | ___ \n" + " | | | '_ \\| | | | | | | | |/ / |\n" + " | |____| | | | | |_| | |_| | <| |\n" + " \\_____|_| |_|_|\\__, |\\__,_|_|\\_\\_|\n" + " __/ | \n" + " |___/ \n" + `杭电信工教务成绩分析脚本-v${VERSION}\n` + "Copyright©ChiyukiRuon\n" + "https://chiyukiruon.com") const newElement = document.createElement('div') newElement.innerHTML = `<table id="score-detail" style="width: 100%; font-size: medium; text-align: left;"><tr style="color: rgb(145, 147, 152); height: 40px"><th><div class="table-text">科目</div></th><th><div class="table-text">最终成绩</div></th><th><div class="table-text">平时成绩</div></th><th><div class="table-text">考核成绩</div></th><th><div class="table-text">学分</div></th><th><div class="table-text">开课学院</div></th></tr>${createTableValue(r###ltList)}</table>` newElement.style.width = '100%' targetElement.parentNode.insertBefore(newElement, targetElement) // 插入切换原始表格和详细成绩表格和复制__VIEWSTATE的按钮 // 定义按钮容器及样式 const btnContainer = document.createElement('div') btnContainer.style.display = 'flex' btnContainer.style.justifyContent = 'flex-end' // 显示绩点 const showGP = document.createElement('div') showGP.innerHTML = `绩点(仅供参考):${calcGP(r###ltList)}` showGP.className = 'info-group' // 定义切换表格按钮 const changeBtn = document.createElement('div') changeBtn.innerText = '切换表格' changeBtn.className = 'btn-group' // 定义复制VIEWSTATE按钮 const copyViewState = document.createElement('div') copyViewState.innerText = '将 __VIEWSTATE 复制到剪切板' copyViewState.className = 'btn-group' // 定义设置###连接保活按钮 const enablePingBtn = document.createElement('div') enablePingBtn.innerText = '开启「防止###自动断开」' enablePingBtn.className = 'btn-group' enablePingBtn.style.display = enablePing?'none':'' const disablePingBtn = document.createElement('div') disablePingBtn.innerText = '关闭「防止###自动断开」' disablePingBtn.className = 'btn-group' disablePingBtn.style.display = enablePing?'':'none' // 注册按钮容器以及按钮 btnContainer.appendChild(showGP) btnContainer.appendChild(changeBtn) btnContainer.appendChild(copyViewState) btnContainer.appendChild(enablePingBtn) btnContainer.appendChild(disablePingBtn) newElement.parentNode.insertBefore(btnContainer, newElement) // 注册按钮点击事件 changeBtn.addEventListener('click', switchTable) copyViewState.addEventListener('click', function (){ const viewstate = document.getElementById('__VIEWSTATE') if (viewstate) { GM_setClipboard(viewstate.value) GM_notification({ title: '杭电信工教务成绩分析', text: '__VIEWSTATE 已复制到剪切板', timeout: 10000 }) }else { GM_notification({ title: '杭电信工教务成绩分析', text: '__VIEWSTATE 复制失败。无法获取 __VIEWSTATE', timeout: 10000 }) } }) enablePingBtn.addEventListener('click', () => { GM_setValue('enablePing', true) enablePing = true enablePingBtn.style.display = 'none' disablePingBtn.style.display = '' keep###Connection() }) disablePingBtn.addEventListener('click', () => { GM_setValue('enablePing', false) enablePing = false enablePingBtn.style.display = '' disablePingBtn.style.display = 'none' clearInterval(ping) }) // 更新网页前清除定时器防止定时器堆叠 window.addEventListener('beforeunload', () => { clearInterval(ping) }) const copyright = document.createElement('div') copyright.innerHTML = `<div><a href="https://github.com/ChiyukiRuon/hziee-score-detail-js/" target="_blank">杭电信工教务成绩分析脚本v${VERSION}</a> | © <a href="https://chiyukiruon.com" target="_blank">ChiyukiRuon</a></div>` copyright.className = 'copyright' newElement.parentNode.insertBefore(copyright, targetElement) } })();