您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
这是优化上海大学选课界面上课程表部分的脚本
// ==UserScript== // @name SHU选课界面优化 // @namespace https://sfkgroup.github.io/ // @version 0.2 // @description 这是优化上海大学选课界面上课程表部分的脚本 // @author SFKgroup // @match https://jwxt.shu.edu.cn/jwglxt/xsxk/* // @grant GM_log // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @icon https://newsso.shu.edu.cn/static/images/ico.jpg // @license LGPL // ==/UserScript== (function () { // 本程序仅适用于上海大学本科教学管理信息服务平台-自主选课页面 var week2id = { '星期一': '1', '星期二': '2', '星期三': '3', '星期四': '4', '星期五': '5', '星期六': '6', '星期日': '7', } var lesson = [] // 全局课程变量 var is_painting = false // 绘制锁 // 文本哈希编码函数 String.prototype.hashCode = function () { let hash = 0; if (this.length === 0) return hash; for (let i = 0; i < this.length; i++) { let char = this.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash); }; // 获取课程的唯一颜色 function get_colour(hash_data) { let h = Math.floor((hash_data / 179) % 360); // H 取值范围为[0,259] let s = Math.floor((hash_data / 997) % 47 + 53); // S 取值范围为[53,100]% let l = Math.floor((hash_data / 97) % 37 + 33); // L 取值范围为[33,70]% return `hsl(${h},${s}%,${l}%)` } // 绘制不存在课程 function mk_lost_lesson(lesson){ let class_name = lesson.name let hash_data = lesson.hash let teacher = lesson.teacher let lesson_time = lesson.raw_time.join('<br>') // <div id="right" class="outer_xkxx_list"> let lesson_html = `<h6 id="${hash_data}">${class_name}<span class="pull-right"></span></h6> <table class="right_table_head"><thead><tr><td class="h_sxbj">选上否</td><td class="h_jxb">教学班</td><td class="h_teacher">教师</td><td class="h_time">上课时间</td><td class="h_time">操作</td></tr></thead></table> <ul id="right_ul" class="list-group" data-kklxdm="01" data-listidx="0"> <li id="right_3463C024895BB241E063F0000A0A7DA9" class="list-group-item" data-itemidx="0"style="cursor: pointer;"><div class="item" style="cursor: pointer;"><table width="100%"><tbody><tr> <td><p class="sxbj"><font color="red">!被删选!</font></p></td> <td><p class="jxb popover-demo" title="${class_name}">${class_name.slice(0, 3)}...</p></td> <td><p class="teachers" title="${teacher}"><span>${teacher}</p></td> <td><p class="time">${lesson_time}</p></td> <td><p class="but"><button class="btn btn-danger btn-sm" id="unselect_${hash_data}"type="button">不再显示</button></p> </td></tr></tbody></table></div></li></ul>` let a_lesson_dom = document.createElement("div") a_lesson_dom.innerHTML = lesson_html a_lesson_dom.setAttribute("id", "right") a_lesson_dom.setAttribute("class", "outer_xkxx_list") document.querySelector(".right_div").appendChild(a_lesson_dom) a_lesson_dom.querySelector(`#unselect_${hash_data.replace(':','\\:')}`).addEventListener("click", function () { let recent_lessons = get_history_table() GM_log("Close "+recent_lessons[hash_data].name) delete recent_lessons[hash_data] set_history_table(recent_lessons) a_lesson_dom.remove() }) return a_lesson_dom.querySelector("h6") } // 获取所有已选课程信息 function get_lessons() { let lessons = get_history_table() // 遍历右边栏中的所有已选择课程 let lesson_dom = document.querySelector(".right_div").children for (let i = 0; i < lesson_dom.length; i++) { let complex_time = [] let name = '' let teacher = '' let hash_id = '' if (lesson_dom[i].querySelector("ul > li > div > table > tbody > tr > td:nth-child(1) > p").innerText == "!被删选!") { // GM_log("Pass "+lesson_dom[i]) continue } try { // 提取原始课程时间信息 complex_time = lesson_dom[i].querySelector("ul > li > div > table > tbody > tr > td:nth-child(6) > p").innerHTML.split('<br>') // 提取课程名称 name = lesson_dom[i].querySelector("ul > li > div > table > tbody > tr > td:nth-child(4) > p").getAttribute('title') // 提取任课教师姓名 teacher = lesson_dom[i].querySelector("ul > li > div > table > tbody > tr > td:nth-child(5) > p > span").innerText // 获取课程的hash值 hash_id = 'CLASS:' + name.hashCode() + '-' + teacher.hashCode() + '-' + Math.floor((name + teacher).hashCode()) // 为课程添加id(用作跳转的标记) lesson_dom[i].querySelector("h6").setAttribute('id', hash_id) } catch (e) { GM_log("Read Lesson Error") } let class_time = [] // 按照 星期X第1-2节 {...} 为基本单元,遍历原始课程时间信息 for (let j = 0; j < complex_time.length; j++) { // 忽略无时间信息的课程 if (complex_time[j] == '--') { class_time.push({ 'date': null, 'time': null, 'week': null }) break } // 提取课程时间格式为 星期X第1-2节 {...} 的课程时间的星期数和节数 let res = { 'date': complex_time[j].split("第")[0], 'time': complex_time[j].split("第")[1].split('节')[0].split('-') } // 提取课程时间格式为 星期X第1-2节 {...} 的课程时间的周数 // {...} 有 {1-8周} 和 {1周,3周,5周} 两种表示方式 let week_raw = complex_time[j].split('{')[1].split('周')[0] if (week_raw.indexOf('-') != -1) { // 1-8 等连续表示的数据中含有'-' res.week = week_raw.split('-') res.continous = true // 连续地表示周数 } else { let weeks = complex_time[j].split('{')[1].replace('}', '').split(',') for (let k = 0; k < weeks.length; k++) { weeks[k] = weeks[k].replace('周', '') // JS一次replace智能替换掉一个“周”字 } res.week = weeks res.continous = false // 离散地表示周数 } class_time.push(res) } // 添加单个课程的所有信息 lessons[hash_id] = { 'index': i, 'name': name, 'teacher': teacher, 'time': class_time, 'hash': hash_id, 'colour': get_colour(name.hashCode()), 'title_dom': lesson_dom[i].querySelector("h6"), 'raw_time': complex_time, 'is_history' : false } } set_history_table(lessons) // GM_log(lessons) return lessons } // 高亮课程(在跳转完成后用) function high_light(dom, colour) { let ori_style = dom.getAttribute("style") ori_style = dom.setAttribute("style", ori_style + ';background-color:' + colour) setTimeout((dom, ori_style) => { dom.setAttribute("style", ori_style) }, 2560, dom, ori_style) // 高亮时间为2560ms } // 初始化课表 function init_table() { // 删除原有单元格的padding样式 document.querySelector("#xskbtable").setAttribute("class", 'table table-bordered tab-bor-col-1') for (let i = 1; i <= 7; i++) { for (let j = 1; j <= 12; j++) { // 遍历所有单元格 let ele = document.querySelector(`#td_${i}-${j}`) ele.setAttribute("style", "padding:0px") // 添加div确保水平排列 let innerHtml = '<div style="display:flex">' // 添加16份子元素表示16周 for (let k = 1; k <= 16; k++) { // a标签负责跳转,嵌套div显示颜色 (无课程默认颜色为#ccc) // 添加a标签id方便绘制课程的时候查询 innerHtml += `<a href="#" id="a_${i}-${j}-${k}" style="width:6.25%;height:32px;"><div style="width:100%;height:100%;background-color:#ccc"></div></a>` } ele.innerHTML = innerHtml + '</div>' // 结束div } } // 自动删除原有的图例 let sign = document.querySelector("#xskbtable > tbody > tr:nth-child(14)") if (sign) sign.remove() } // 将课程绘制入课程表 function paint(lessons) { // 遍历课程 for (let key in lessons) { let lesson_now = lessons[key] // 油猴会在存储的Object里面加入我们不需要的key,所以这里判断一下 if (!key.startsWith("CLASS:")) { // GM_log("Droup " + key) continue } // 判断是否为删选课程 if (lesson_now.is_history) { if (document.getElementById(lesson_now.hash) == null) { lesson_now.title_dom = mk_lost_lesson(lesson_now) } lesson_now.index = document.querySelector("#mCSB_1_container > div > div.right_div").children.length } // 遍历时间条数 (星期几) for (let t = 0; t < lesson_now.time.length; t++) { let lesson_time = lesson_now.time[t] if (lesson_time.date == null) continue; let date = week2id[lesson_time.date] // 遍历课程数 (第几节课) for (let c = 1 * lesson_time.time[0]; c <= 1 * lesson_time.time[1]; c++) { // 判断课程是连续上几周的格式,还是离散上几周的格式 if (lesson_time.continous) { // 遍历上的周数 for (let w = 1 * lesson_time.week[0]; w <= 1 * lesson_time.week[1]; w++) { paint_cell(lesson_now, date, c, w) } } else { // 遍历上的周数 for (let kw = 0; kw < lesson_time.week.length; kw++) { paint_cell(lesson_now, date, c, lesson_time.week[kw]) } } } } } } // 绘制最小的单元格 function paint_cell(lesson_now, date, c, w) { // id格式:a_{周几}-{第几节课}-{第几周} let ele = document.querySelector(`#a_${date}-${c}-${w}`) // 如果没有该单元格则跳过绘制 if (!ele) { GM_log(`#a_${date}-${c}-${w}`) return; } // 设置单元格颜色为课程颜色 ele.querySelector("div").setAttribute("style", "width:100%;height:100%;background-color:" + (lesson_now.is_history ? "#666" : lesson_now.colour)) // 设置单元格跳转课程链接 ele.setAttribute("href", "#" + lesson_now.hash) // 设置鼠标悬停文本 ele.setAttribute("title", lesson_now.name + ':' + lesson_now.teacher) // 添加点击事件,适配高亮行为 ele.addEventListener('click', function () { // 高亮被跳转的课程 high_light(lesson_now.title_dom, lesson_now.colour) // 修改侧边栏滚动位置(选课网站侧边栏的滚动不是靠页面滚动实现的,故而在传统标签跳转的基础上还要再修改一次页面位置) // 500px 为课程表高度,每个课程高度为150px document.querySelector("#mCSB_1_container").setAttribute('style', `position: relative; top: -${500 + 150 * lesson_now.index}px; left: 0px; width: 740px;`) }) } // 渲染自定义的课表 function main() { init_table(); // 添加提示 if (!document.getElementById("MonkeyINFO")) { let info_dom = document.createElement('h4'); info_dom.innerText = "注:收起选课信息再展开可以刷新课表" info_dom.setAttribute("id", "MonkeyINFO") document.querySelector("#mCSB_1_container > div").prepend(info_dom); } paint(lesson); is_painting = false; // 取下绘制锁 } // 获取课表并延时启动绘制 function time_out_main() { // 确认右边栏的显示状态 if (document.querySelector("#choosedBox > div > div.outer_left > span").getAttribute("class").indexOf('right') == -1) return; // GM_log("Paint") is_painting = true;// 加上绘制锁 lesson = get_lessons(); // 延时启动绘制(给原始课表渲染的时间) setTimeout(main, 300) } // 检查课表是否正常渲染 function check_table() { if (is_painting) return; // 如果在绘制中则不检查 // 如果右侧栏未展开则不检查 if (document.querySelector("#choosedBox > div > div.outer_left > span").getAttribute("class").indexOf('right') == -1) return; // 检查周日最后一节课的课表渲染是否正常 if (document.querySelector("#td_7-12 > div")) return; // 如果不正常则重新渲染 time_out_main() } // 设置历史课表 function set_history_table(lessons = {}){ let lesson_copy = JSON.parse(JSON.stringify(lessons)); for (let key in lesson_copy){ lesson_copy[key].is_history = true } GM_setValue('lessons', lesson_copy) // GM_log(lessons) } // 读取历史课表 function get_history_table(){ let lessons = GM_getValue('lessons', []) return lessons } // 删选课程测试 function test_lost_lesson(){ let lesson = get_history_table() lesson["CLASS:114514-1919810-1145141919810"] = {"index": 0,"name": "周末摸鱼指南","teacher": "费雪","time": [{"date": "星期六","time": ["3","4"],"week": ["1","16"],"continous": true},{"date": "星期日","time": ["3","4"],"week": ["13","16"],"continous": true}],"hash": "CLASS:114514-1919810-1145141919810","colour": "rgb(102,204,255)","title_dom": {},"raw_time": ["星期六第3-4节{1-16周}","星期日第3-4节{13-16周}"],"is_history": false} set_history_table(lesson) } // 注册插件命令 GM_registerMenuCommand("清空历史课表", set_history_table); GM_registerMenuCommand("保存当前课表", function () { set_history_table(get_lessons()) }); //GM_registerMenuCommand("删选课程测试", test_lost_lesson); // 主程序初始化阶段 // 等待页面加载完成 window.onload = function () { // 检查是否处于选课阶段 let ban_info = document.querySelector("#innerContainer > div.panel.panel-info > div.panel-body > div > span") if (ban_info && ban_info.innerText.indexOf("不属于选课阶段") != -1) { GM_log("不属于选课阶段!"); // 如非选课阶段则绘制该学生的课表 let class_table_dom = document.createElement('iframe'); class_table_dom.setAttribute("src", "/jwglxt/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default") class_table_dom.setAttribute("style", "width:100%;height:300px;border:none;") document.querySelector("#innerContainer > div.panel.panel-info").appendChild(class_table_dom) return; } setTimeout(function () { // 添加右边栏展开事件监听器 document.querySelector("#choosedBox > div > div.outer_left").addEventListener('click', time_out_main) // 添加循环检测课表是否绘制正常(主要应对退选课程的刷新问题) setInterval(check_table, 200); }, 200); } })();