您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
tool helping alot with subject registation
// ==UserScript== // @name Scheduling Subject Registration // @namespace http://tampermonkey.net/ // @version 2024-07-03 // @description tool helping alot with subject registation // @author Minh Triet (Alex Ng) // @match https://uis.ptithcm.edu.vn/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @require http://code.jquery.com/jquery-3.6.0.min.js // @grant GM_addStyle // @grant unsafeWindow // @run-at document-start // @license MIT // ==/UserScript== GM_addStyle(` .card-header { border-radius: 15px !important; border-bottom: 1px solid rgba(0, 0, 0, 0.125) !important; padding: 0.75rem 1.25rem !important; margin-bottom: 0 !important; } .container-fluid { border-radius: 15px !important; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3) !important; } .card { border: 0px solid rgba(0, 0, 0, 0.125) !important; border-radius: 15px !important; box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.125) !important; } .btn-primary { border: 0px solid rgba(0, 0, 0, 0.125) !important; border-radius: 15px !important; box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.125) !important; } html { background: #f2f2f2; } body { background: inherit !important; margin: 0; font-family: "Inter", sans-serif; } .banner { background-color: #fff !important; } .banner { display: block; width: 100% !important; height: 728px !important; border-bottom-left-radius: 15px !important; border-bottom-right-radius: 15px !important; } .float { position: fixed; z-index: 1; width:60px; height:60px; bottom:40px; right:40px; color: #000000; background: #FFF; border-radius: 50%; border: none; transition: box-shadow 400ms cubic-bezier(0.2, 0, 0.7, 1), transform 200ms cubic-bezier(0.2, 0, 0.7, 1); } .float:after { font-size: 2.5em; line-height: 1.1em; } .float:hover { box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.22) rgba(0, 0, 0, 0.36) rgba(0, 0, 0, 0.3) !important; transform: scale(1.05); } .editTKB { background-color:#2fa4e7 !important; } .editTKB > i { color: #fff !important; } #tkb_div { height: auto; width: 80% !important; margin-left: 10% !important; margin-right: 10% !important; margin-top: 60px; align-items: center; justify-content: center; background-color: #fefefe; } .danhsachmonhoc_text { text-align: center; margin: auto; } .tkb_preview_table > thead { background-color: #2fa4e7; } .tkb_preview_table > thead > tr > th { color: #fff; padding: 0.5px; text-align: center; } .cellqh { border: 1px solid green; max-width: 14px; max-height: 6px; font-size: 13px; position: relative; border-bottom: 1px dotted black; padding: 2px; text-align: center; } .tooltiptext { font-size: 8px; } .cellqh .tooltiptext { visibility: hidden; width: 120px; background-color: black; color: #fff; text-align: center; padding: 5px 0; border-radius: 6px; position: absolute; z-index: 1; display: block; float: left; } .cellqh:hover .tooltiptext:not(:empty) { visibility: visible; } .cellqh:hover .tooltiptext { visibility: visible; } .starttimerow { padding: 1px; vertical-align: middle; border-top: 0.2px solid #2fa4e7; background-color: #2fa4e7; color: #fff; max-width: 30px; max-height: 10px; font-size: 8px; } .chonngaydiv { text-align: center; margin-bottom: 10px; background-color: #fefefe; border-collapse: collapse; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.02); align-items: center; justify-content: center; margin-left: auto; margin-right: auto; margin-top: 30px; } .danhsachmonhoc { display: flex; justify-content: center; align-items: center; margin-bottom: 10px; flex-wrap: wrap; background-color: #fefefe; border-collapse: collapse; flex-direction: column; } .dropdownlecture { text-align: center; margin-bottom: 10px; margin-top: 30px; background-color: #fefefe; border-collapse: collapse; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.02); background-color: #fefefe; margin-left: 30%; margin-right: 30%; width: 40%; } input[type="date"]::-webkit-datetime-edit, input[type="date"]::-webkit-inner-spin-button, input[type="date"]::-webkit-clear-button { color: #fff; position: relative; } input[type="date"]::-webkit-datetime-edit-year-field { position: absolute !important; border-left:1px solid #8c8c8c; padding: 2px; color:#000; left: 56px; } input[type="date"]::-webkit-datetime-edit-month-field { position: absolute !important; border-left:1px solid #8c8c8c; padding: 2px; color:#000; left: 26px; } input[type="date"]::-webkit-datetime-edit-day-field { position: absolute !important; color:#000; padding: 2px; left: 4px; } .timetables { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; } .timetable-container { width: calc(100% / 5 - 20px); margin: 10px; } .timetable-container h2 { text-align: center; } .timetable { width: 100%; border-collapse: collapse; } .timetable th, .timetable td { border: 1px solid #ddd; padding: 8px; text-align: center; } .timetable th { background-color: #f2f2f2; } @media (max-width: 1200px) { .timetable-container { width: calc(100% / 4 - 20px); } } @media (max-width: 992px) { .timetable-container { width: calc(100% / 3 - 20px); } } @media (max-width: 768px) { .timetable-container { width: calc(100% / 2 - 20px); } } @media (max-width: 576px) { .timetable-container { width: 100%; } } `); window.onload = function() { unsafeWindow.scraptSubjects = scraptSubjects; unsafeWindow.drawTable = drawTable; unsafeWindow.process = process; const floatingButton = document.createElement('button'); floatingButton.innerHTML = `<button class="float"><i class="fa fa-cogs"></i></button>`; floatingButton.onclick = function() { var float = $(".float"); float.addClass("editTKB"); process() ensureDropdownEventListener() ensureCheckBoxEventListener() } document.body.appendChild(floatingButton) // we have input's class name, so how to combine with? } class Subject { constructor(subjectCode, subjectName, groupCode, teamCode, classCode, timeTable) { this.subjectCode = subjectCode this.subjectName = subjectName this.groupCode = groupCode this.teamCode = teamCode.trim().length >= 1 ? teamCode : '' this.classCode = classCode this.timeTable = reformatRoutine(timeTable) } } const registeredSubjects = new Map(); const subjects = new Map(); const nameStoring = new Map(); const storeCheckedSubjects = new Set(); let dateStart = new Date( '2024-08-12' ); const tables = Array.from({ length: 28 }, () => Array.from({ length: 14 }, () => Array.from({ length: 7 }, () => []))); function scraptSubjects() { let nameStoring = new Map(); unsafeWindow.subjects = subjects; $(".custom-control").attr("style", "display :none !important"); const tableSubjects = $('tbody')[28] const rows = tableSubjects.children for (let i = 0; i < rows.length; i++) { // enable checked for this input const subject = new Subject( rows[i].children[1].innerText, rows[i].children[2].innerText, rows[i].children[3].innerText, rows[i].children[4].innerText, rows[i].children[6].innerText, rows[i].children[9].innerText ) nameStoring.set(rows[i].children[1].innerText, rows[i].children[2].innerText) const key = subject.subjectCode + subject.groupCode + subject.teamCode subjects.set ( key, subject ); // key -value // modify checkbox let form = $(rows[i]).find("form") let tag = `<input type='checkbox' class='editCheckBoxInput' value="${key}">` form.append(tag) } subjects.forEach((value, key) => { console.log(key, value) }) // add dropdown var dropDown = $(".dropdownlecture"); dropDown.empty(); dropDown.append('<option value ="all" class ="">All</option>'); for (let [key, value] of nameStoring) { dropDown.append(`<option value ="${key}">${key} ${value}</option>`); } alert("Đã tải xong dữ liệu môn học"); } function ensureDropdownEventListener() { let inputEle; var timer = setInterval(() => { if ($('.dropdownlecture').length == 0) { return; } else { $(".dropdownlecture").change(function(event) { if (inputEle == undefined) { inputEle = document.getElementsByClassName("form-control form-control-sm small text-secondary ng-untouched ng-pristine ng-valid")[0] } if (event.target.value == "all") { inputEle.value = ""; } else { inputEle.value = event.target.value } const inputEvent = new Event('input', { bubbles: true, cancelable: true, }); inputEle.dispatchEvent(inputEvent); reinitializeCheckboxInput() }) clearInterval(timer); } }, 200) } function ensureCheckBoxEventListener() { var time = setInterval(() => { if ($('.editCheckBoxInput').length == 0) { return; } else { $(".editCheckBoxInput").change(function(event) { const key = event.target.value console.log(key); const subject = subjects.get(key) if (event.target.checked) { if (registeredSubjects.has(subject.subjectCode)) { alert("Môn học đã được đăng ký"); $(event.target).prop('checked', false); } else { console.log(subject) addSubjectToTable(subject); registeredSubjects.set(subject.subjectCode, subject); storeCheckedSubjects.add(key); $(".danhsachmonhoc").empty(); for (let [key, value] of registeredSubjects) { $(".danhsachmonhoc").append(`<div class="${value.subjectCode}">${value.subjectCode} | ${value.subjectName} | ${value.classCode}</div>`); } } } else { registeredSubjects.delete(subject.subjectCode); storeCheckedSubjects.delete(key); removeSubjectFromTable(subject); $(".danhsachmonhoc").empty(); for (let [key, value] of registeredSubjects) { $(".danhsachmonhoc").append(`<div class="${value.subjectCode}">${value.subjectCode} | ${value.subjectName} | ${value.classCode}</div>`); } } }) clearInterval(time); } }, 200) } function addSubjectToTable(subject) { // add to table times subject.timeTable.forEach((item) => { console.log(item); let distanceStart = caculateDistanceTwoDays(item.start, dateStart); let distanceEnd = caculateDistanceTwoDays(item.end, dateStart); let day = item.day; let start = Math.floor(distanceStart / 7); let end = Math.floor(distanceEnd / 7); console.log(start, end); while (start <= end) { for (let i = item.time[0] - 1; i <= item.time[1] - 1; i++) { tables[start][i][day - 2].push({ subjectCode: subject.subjectCode, subjectName: subject.subjectName, classCode: subject.classCode }); // console.log(`start: ${start} i: ${i} day: ${day} subject: ${subject.subjectCode} tables: ${tables[start][i][day]}`) } start++; } }) updateTable(tables) } function removeSubjectFromTable(subject) { // remove from table times subject.timeTable.forEach((item) => { let distanceStart = caculateDistanceTwoDays(item.start, dateStart); let distanceEnd = caculateDistanceTwoDays(item.end, dateStart); let day = item.day; let start = Math.floor(distanceStart / 7); let end = Math.floor(distanceEnd / 7); while (start <= end) { for (let i = item.time[0] - 1; i <= item.time[1] - 1; i++) { let index = tables[start][i][day - 2].findIndex((item) => item.subjectCode == subject.subjectCode) tables[start][i][day - 2].splice(index, 1); } start++; } }) updateTable(tables) } function updateTable(tables) { const states = { available: { color: "white", icon: "+", colorText: "black" }, full: { // don't have any subject color: 'green', icon: "o", colorText: "white" }, // have one subject // have more than one subject over: { color: 'red', icon: "x", colorText: "white" } } for (let i = 0; i < 28; i++) { for (let r = 0; r < 14; r++) { for (let c = 0; c < 7; c++) { let id = "tkb" + i + r + c; let cell = $(`#${id}`); let state = states.available; if (tables[i][r][c].length == 1) { state = states.full; } else if (tables[i][r][c].length > 1) { state = states.over; } let hoverText = tables[i][r][c].map((item) => { return `${item.subjectCode} - ${item.subjectName} - ${item.classCode}` }).join("\n") cell.css('background-color', state.color); cell.css('color', state.colorText); cell.text(state.icon); cell.attr('title', hoverText ); // hover event for cell } } } } function reformatRoutine(str) { try { if (str.trim() == "") return []; str = str.toLowerCase(); const dayInWeek = new Map([ ["thứ 2", 2], ["thứ 3", 3], ["thứ 4", 4], ["thứ 5", 5], ["thứ 6", 6], ["thứ 7", 7], ["chủ nhật", 8] ]); return str.split("\n").map((item) => { if (item.indexOf("đến") == -1) { let [day, time, date] = item.split(","); let [startDay, startMonth, startYear] = date.split("/"); let [start, end] = time.replace("tiết", "").trim().split("->").map((item) => parseInt(item)); startYear = startYear.replace(/\D/g, ""); return { day: dayInWeek.get(day), time: [start, end], start: new Date(`20${startYear.trim()}-${startMonth.trim()}-${startDay.trim()}`), end: new Date(`20${startYear.trim()}-${startMonth.trim()}-${startDay.trim()}`) }; } let [day, time, date] = item.split(","); let [start, end] = date.split("đến"); let [startDay, startMonth, startYear] = start.split("/"); let [endDay, endMonth, endYear] = end.split("/"); endYear = endYear.replace(/\D/g, ""); time = time.replace("tiết", "").trim().split("->").map((item) => parseInt(item)); return { day: dayInWeek.get(day), time: time, start: new Date(`20${startYear.trim()}-${startMonth.trim()}-${startDay.trim()}`), end: new Date(`20${endYear.trim()}-${endMonth.trim()}-${endDay.trim()}`) }; }); } catch (e) { console.log(e); return []; } } function drawTable() { let rows = 14; // Number of rows representing the periods let cols = 7; // Number of columns representing the days let tables = []; // Creating the main div container for the timetable let tkb_div = $("<div id='tkb_div' class='flex flex-wrap justify-center flex-row items-center'></div>"); // draw tables that have 20 tables 1 table represent 1 week, and 1 day have 14 rows for (let i = 0; i < 28; i++) { let table_id = "tkbPreview" + i + 1; tables[i] = $('<table style="text-align:center;border-collapse: collapse;" class="tkb_preview_table" id="' + table_id + '"><thead> <th></th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th></th></thead><tbody>'); for (let r = 0; r < rows; r++) { let start_time_in_hr = r +1; let tkb_separator = r % 2 ? 'border-bottom:2px solid #2fa4e7;' : ''; let tr = $('<tr style="height:1px;' + tkb_separator + '"><td class="starttimerow">' + start_time_in_hr + '</td>'); for (let c = 0; c < cols; c++) { let id = "tkb" + i + r + c; $('<td class="cellqh" id="' + (id) + '">+</td>').appendTo(tr); } tr.appendTo(tables[i]); } $('</tbody></table>').appendTo(tables[i]); tkb_div.append(tables[i]); } tkb_div.attr("style", "display:flex; flex-wrap: wrap; padding: 10px; justify-content: center; align-items: center;") // Adding date picker and dropdown for lectures let date_pickerSection = $("<div id= 'datepickersection'></div>"); date_pickerSection.append('<div class="chonngaydiv"><label for="datetimepicker">Chọn ngày bắt đầu tuần đầu tiên (Xem trong TKB tuần)</label><input class="inputdate" id="datetimepicker" type="date" value="2024-08-12"></input><br></div>'); date_pickerSection.append('<select class="dropdownlecture"><option value="" class="label_dropdownlecture">Chọn môn</option></select>'); date_pickerSection.append('<div class="danhsachmonhoc_text"><strong>Các môn đã đăng ký</strong></div>'); date_pickerSection.append('<div class="danhsachmonhoc"></div>'); // Adding the timetable to the page $("div.card-body.p-0 div.row.d-flex.justify-content-center.text-nowrap.pt-1").prepend(date_pickerSection); $("div.card-body.p-0 div.row.d-flex.justify-content-center.text-nowrap.pt-1").prepend(tkb_div); let introduce = $("<div class='introduce'></div>") introduce.append("<h3>Made by Minh Triet(Alex Ng)</h3>") introduce.append("<h3>Tool hỗ trợ sắp xếp thời gian biểu PTITHCM</h3>") introduce.append("<h3>1. Chọn thời gian bắt đầu của kì học mới</h3>") introduce.append("<h3>2. Chọn môn học muốn đăng ký</h3>") introduce.append("<h3>Note: Ô màu trắng thì chưa có môn nào chiếm chỗ</h3>") introduce.append("<h3>Ô màu xanh thì đang có 1 môn, Ô màu đỏ là ô không hợp lệ(chứa 2 môn)</h3>") introduce.append("<h3>Không chịu trách nhiệm dưới bất kì hình thức nào</h3>") introduce.append("<h3>Mình mong sẽ nhận được feedbacks từ các bạn, facebook: https://fb.com/triet.nguyen.39904181 </h3>") introduce.attr("style", "display:flex; ; padding: 10px; justify-content: center; align-items: center; flex-direction: column") $("div.card-body.p-0 div.row.d-flex.justify-content-center.text-nowrap.pt-1").prepend(introduce); } function process() { if ($("#tkb_div").length == 0) { drawTable(); } if (subjects.size == 0) { scraptSubjects(); } } function reinitializeCheckboxInput() { // wait for the input to be loaded var timer = setInterval(() => { if ($('.editCheckBoxInput').length == 0) { return; } else { const tableSubjects = $('tbody')[28] const rows = tableSubjects.children // delete all checkbox // delete all checkbox, consist old or new // for (let i = 0; i < rows.length; i++) { let form = $(rows[i]).find("form") // replace this input with new input let key = rows[i].children[1].innerText + rows[i].children[3].innerText + rows[i].children[4].innerText; let currTag = $(form).find("input"); currTag.attr("value", key); if (storeCheckedSubjects.has(key)) { currTag.prop('checked', true); } else { currTag.prop('checked', false); } } clearInterval(timer); } }, 200) } function caculateDistanceTwoDays(day1, day2) { const parsedDate1 = new Date(day1); const parsedDate2 = new Date(day2); // Calculate the difference in milliseconds const differenceInMilliseconds = parsedDate2 - parsedDate1; // Convert milliseconds to days const millisecondsInOneDay = 1000 * 60 * 60 * 24; const differenceInDays = differenceInMilliseconds / millisecondsInOneDay; return Math.abs(differenceInDays); } // listener for choosing date in date tag $(document).ready(function() { $(document).on('change', '.inputdate', function(event) { dateStart = new Date(event.target.value); }) })