您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
给满员、冲突的课程自动上色
// ==UserScript== // @name BIT-物理实验中心-实验选修 // @namespace http://tampermonkey.net/ // @version 1.2.0 // @description 给满员、冲突的课程自动上色 // @license GPL-3.0-or-later // @supportURL https://github.com/YDX-2147483647/BIT-enhanced/issues // @author Y.D.X. // @match http://10.133.22.200:7100/XPK/StuCourseElective // @grant none // ==/UserScript== // 修改函数`my_conflict_referee()`中的变量`bans`,或者完全重构`my_conflict_referee()` (function () { 'use strict' /** * 判定是否冲突 * @param {Course} course * @returns {boolean} */ function my_conflict_referee (course) { // 如果您看不懂这段代码,也许读读`BIT-物理实验中心-实验选修.md`会有帮助。 if (course.class_time.week >= 8 && course.class_time.week <= 11 && course.class_time.day === 3 && course.class_time.section === 5) { return true } /** * 不能上的时间 * @type {number[][]} [[星期几(1-7), 第几大节(1-5)]] */ const bans = [ [1, 2], [1, 4], [2, 2], [2, 3], [2, 4], [2, 5], [3, 2], [3, 3], [4, 1], [4, 2], [4, 3], [5, 2] ] return bans.find(([day, section]) => day === course.class_time.day && section === course.class_time.section) } /** * 课程类别,用于 CSS class */ const Categories = { full: 'full-course', conflict: 'conflict-course', available: 'available-course' } function add_style_sheet () { const sheet = document.createElement('style') sheet.innerHTML = ` tr.${Categories.full} { background-color: #FF000040; } tr.${Categories.conflict} { background-color: yellow; } .datagrid-view1 tr.${Categories.conflict}.${Categories.full} { background-color: #FF000040; } .datagrid-view2 tr.${Categories.conflict}.${Categories.full} { background: linear-gradient(to left, yellow, 20%, #FF000040); } tr.${Categories.available} { background-color: greenyellow; } ` document.head.appendChild(sheet) } /** * 解析汉字数字 * @description 只支持一位数 * @param {string} s * @returns {number} */ function parse_int_zh (s) { return '零一二三四五六七八九'.indexOf(s) } /** * 解析时间 * @param {string} time * @returns {number[]} */ function parse_time (time) { return time.split(':').map(x => Number(x)) } /** * 选课表格的一行 * @description 因为选课表格设计得太奇怪了:分明只有一个表格,实际却是四个`<table>`…… */ class Course { /** * 解析形如“2/3”的比例 * @param {string} ratio * @returns {number[]} */ static parse_ratio (ratio) { return ratio.split('/').map(x => Number(x)) } /** * 解析上课时间 * @param {string} time 例如“第8周星期一上午第二大节2021-10-11 09:55-12:10” */ static parse_class_time (time) { const match_obj = time.match( /第(?<week>\d+)周星期(?<day>[一二三四五六日])(上午|下午|晚上)第(?<section>[一二三四五])大节(?<date>[-\d]{10}) (?<start>[:\d]{5})-(?<end>[:\d]{5})/) if (!match_obj) { throw Error(`无法识别上课时间:${time}。`) } const groups = match_obj.groups return { week: Number(groups.week), day: '一二三四五六日'.indexOf(groups.day) + 1, section: parse_int_zh(groups.section), date: new Date(groups.date), start: parse_time(groups.start), end: parse_time(groups.end) } } /** * * @param {HTMLElement} left view1 中的半行 * @param {HTMLElement} right view2 中的另外半行 */ constructor (left, right) { /** 实际元素 * @type {HTMLElement[]} */ this._row = [left, right] /** 已选人数 * @type number */ this.student_count = 0 /** 课程容量 * @type number */ this.capacity = 0; [this.student_count, this.capacity] = Course.parse_ratio(this._row[1] .querySelector("td[field='ElectivedNum']").textContent) /** 上课时间 * @type {{ week: number, day: number, section: number, date: Date, start: number[], end: number[] }} */ this.class_time = Course.parse_class_time(this._row[1] .querySelector("td[field='GradeValue'], td[field='value1']").textContent) } /** * 设置类别 * @param {string[]} categories */ _set_category (...categories) { this._row.forEach(side => { side.classList.add(...categories) }) } /** * 去除类别(恢复默认) */ _remove_category () { this._row.forEach(side => { side.classList.remove(...Object.values(Categories)) }) } /** * 根据内容更新类别 * @param {(course: Course) => boolean} conflict_referee 判定是否冲突 */ update_category (conflict_referee = () => false) { this._remove_category() const is_full = this.student_count >= this.capacity const is_conflict = conflict_referee(this) if (is_full) { this._set_category(Categories.full) } if (is_conflict) { this._set_category(Categories.conflict) } if (!is_full && !is_conflict) { this._set_category(Categories.available) } } }; /** * 获取课程列表 * @returns {Course[]} */ function get_courses () { const [left_side, right_side] = [1, 2] .map(n => `#tt .datagrid-view${n} .datagrid-body tbody tr`) .map(selector => Array.from(document.querySelectorAll(selector))) return left_side.map((left, index) => new Course(left, right_side[index])) } function paint () { const courses = get_courses() courses.forEach(c => c.update_category(my_conflict_referee)) } async function add_painter () { const observer_config = { attributes: false, childList: true, subtree: true } // 等待“实验选修”按钮出现 const button = await new Promise((resolve, reject) => { const button_observer = new MutationObserver(() => { // 是的,网页里的确是“lable”而非“label” const _button = document.querySelector('.panel.datagrid .datagrid-view2 .datagrid-body table lable:first-child') if (_button) { button_observer.disconnect() resolve(_button) } }) button_observer.observe(document.body, observer_config) }) // 单击“实验选修”后稍等,会出现选课列表,这时再开始自动上色 button.addEventListener('click', () => { const observer = new MutationObserver(() => { if (document.querySelector('.panel-title')) { new MutationObserver(paint).observe(document.querySelector('#dialogChooseCourses .datagrid-view'), observer_config) observer.disconnect() } }) observer.observe(document.body, observer_config) }) } add_style_sheet() add_painter() })()