您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
听说你抢不到课
// ==UserScript== // @name 东南大学抢课助手修改版 // @namespace http://tampermonkey.net/ // @version 3.3.0 // @description 听说你抢不到课 // @author july // @license MIT // @match newxk.urp.seu.edu.cn/xsxk/elective/grablessons?* // @run-at document-loaded // @icon https://s2.loli.net/2024/12/19/lngsEvZ8tfUdJzr.jpg // ==/UserScript== (function () { // 版本 let version = [3, 3, 0]; // 请求 let request = axios.create(); // 提示 let tip = grablessonsVue.$message; let isRunning = false; let shouldStop = false; // 所选课程 let enrollDict = {}; // 设置 let settings = {}; // 定义默认设置 const defaultSettings = { token: "", savedCourseCodes: "", mode: { isAsync: false, isCyclic: true, cycleCount: -1, // -1表示无限循环 enableSearch: false, }, interval: { sync: { single: 300, // 同步单次间隔 group: 1000, // 同步分组间隔 }, async: { single: 350, // 异步单次间隔 group: 1000, // 异步分组间隔 }, }, search: { pageSize: 20, // 每页课程数量 pageDelay: 500, // 翻页延迟(ms) }, }; // 挂载的顶层组件 let app = document.getElementById("xsxkapp"); // 组件生成 ((self) => { // 生成组件 self.mount = () => { self.createTag(); self.createPanel(); self.createMask(); self.addEnrollButton(); }; // 生成节点 self.createNode = ({ tagName, text, HTML, obj, ev, children }) => { let node = document.createElement(tagName); if (obj) { for (let key of Object.keys(obj)) { node.setAttribute(key, obj[key]); } } if (text) { node.innerText = text; } if (HTML) { node.innerHTML = HTML; } if (ev) { for (let key of Object.keys(ev)) { node.addEventListener(key, ev[key]); } } if (children) { children.map((x) => node.appendChild(x)); } return node; }; // 生成打开和关闭面板的按钮 self.createTag = () => { let node = self.createNode({ tagName: "div", obj: { class: "slideMenu", style: ` position: fixed; top: 250px; left:30px;width: 40px;z-index: 1314; `, }, children: [ self.createNode({ tagName: "div", obj: { class: "centre-btn item el-icon-date", style: `background-color: #2b2b2b`, }, ev: { mousedown: (e) => { methods.drag(e, node); }, }, }), ], }); app.appendChild(node); }; // 生成面板 self.createPanel = () => { app.appendChild( self.createNode({ tagName: "div", obj: { id: "panel", style: ` position: fixed; right: 0; top:0 ; z-index: 520; width: 350px; height: 100%; background-color: rgba(61,72,105,0.8); display: block; `, }, children: [ self.createNode({ tagName: "hr" }), self.createNode({ tagName: "h1", text: "东大抢课脚本", obj: { style: "color: #c7e6e6; text-align: center", }, }), self.createNode({ tagName: "hr" }), self.createNode({ tagName: "input", obj: { id: "input-box", class: "el-input__inner", style: ` width: 96%; margin-left: 2%; height: 30px `, placeholder: "输入课程代码(不区分大小写),按回车确定", }, ev: { keydown: methods.enter, }, }), self.createNode({ tagName: "div", obj: { id: "list-wrap", style: ` overflow: auto; margin: 10px; border:1px solid white; height: 75% `, }, }), self.createNode({ tagName: "button", obj: { id: "enroll-button", class: "el-button el-button--primary el-button--small is-round", style: ` margin: 20px; position: absolute; right:50%; bottom:5% `, }, text: "一键抢课", ev: { click: async () => { if (shouldStop) { tip({ type: "error", message: "请稍候,正在终止上一个抢课进程", duration: 1000, }); return; } if (isRunning) { await methods.stopEnrolling(); } isRunning = true; methods.updateUIState(); methods.enroll(); }, }, }), self.createNode({ tagName: "button", obj: { id: "settings-stop-button", class: `el-button el-button--${ isRunning ? "danger" : "info" } el-button--small is-round`, style: ` margin: 20px; position: absolute; right:20%; bottom:5% `, }, text: isRunning ? "停止抢课" : "更多设置", ev: { click: async () => { if (isRunning) { await methods.stopEnrolling(); } else { document.getElementById("mask").style.display = "block"; self.updatePopup(settings.mode.enableSearch, settings); } }, }, }), self.createNode({ tagName: "div", obj: { style: ` margin: 20px; position: absolute; right:2%; bottom:1%; color: white; float: right `, }, text: "ver" + version.join("."), }), ], }) ); self.reloadList(); }; // 生成抢课表格 self.reloadList = () => { let list_wrap = document.querySelector("#panel #list-wrap"); list_wrap.innerHTML = ""; if (JSON.stringify(enrollDict) === "{}") { list_wrap.innerHTML = "<h3 style='text-align: center;color:lightblue;margin-top: 50%'>还未选择课程</h3>"; } else { list_wrap.appendChild( self.createNode({ tagName: "table", obj: { width: "100%", border: "1", style: ` background-color: rgba(0,0,0,0); color: lightblue `, }, children: [ self.createNode({ tagName: "tr", obj: { style: ` height: 30px; background-color: #255e95 `, }, HTML: ` <th style="text-align:center;width: 55%">课程</th> <th style="text-align:center;width: 15%">教师</th> <th style="text-align:center;width: 30%">操作</th> `, }), ...Object.keys(enrollDict) .filter( (key) => enrollDict[key].courseBatch === grablessonsVue.lcParam.currentBatch.code ) .map((key) => { return self.createNode({ tagName: "tr", obj: { style: `height: 30px`, }, children: [ self.createNode({ tagName: "td", obj: { style: `text-align: center`, }, text: enrollDict[key].courseName, }), self.createNode({ tagName: "td", obj: { style: `text-align: center`, }, text: enrollDict[key].teacherName, }), self.createNode({ tagName: "td", obj: { style: `text-align: center`, }, children: [ self.createNode({ tagName: "button", text: "删除", obj: { class: "delete-button", style: ` color: red; background: transparent; border: 1px solid red; border-radius: 6px; text-align: center; cursor: pointer; text-decoration: none; margin-right: 2px `, }, ev: { click: () => { const course = enrollDict[key]; delete enrollDict[key]; methods.saveData(); tip({ type: "success", message: `${course.teacherName} 的 ${course.courseName} 已删除`, duration: 1000, }); self.reloadList(); }, }, }), self.createNode({ tagName: "button", text: "更多", obj: { style: ` color: orange; background: transparent; border: 1px solid orange; border-radius: 6px; text-align: center; cursor: pointer; text-decoration: none; margin-left: 2px `, }, ev: { click: () => { document.getElementById("mask").style.display = "block"; self.createPopUp( "详细信息", self.showCourseDetails(enrollDict[key]) ); }, }, }), ], }), ], }); }), ], }) ); } }; // 生成遮罩 self.createMask = () => { let node = self.createNode({ tagName: "div", obj: { id: "mask", style: ` position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: 2002; background-color: rgba(66, 66, 66, 0.6); display: none `, }, ev: { click: () => { node.style.display = "none"; document.querySelectorAll(".temp").forEach((el) => { if (el.parentNode) el.parentNode.removeChild(el); }); }, }, }); app.appendChild(node); }; // 生成弹出窗 self.createPopUp = (title, node, onConfirm, width, height, onExtend) => { const popupNode = self.createNode({ tagName: "div", obj: { class: "temp", style: ` position: fixed; left: ${width ? 50 - 0.5 * width : 30}%; top: ${height ? 50 - 0.5 * height : 30}%; width: ${width || 40}%; height: ${height || 40}%; z-index: 2021; background-color: white; border-radius: 30px; overflow: auto; `, }, children: [ self.createNode({ tagName: "h1", obj: { style: ` margin: 20px 0; width: 100%; text-align: center; `, }, text: title, }), node, self.createNode({ tagName: "div", obj: { style: ` position: absolute; width: 80%; left: 10%; bottom: 10%; display: flex; justify-content: space-between; align-items: center; `, }, children: [ // 左侧按钮组 self.createNode({ tagName: "div", obj: { style: "display: flex; gap: 10%;", }, children: [ ...(onExtend ? [ self.createNode({ tagName: "button", obj: { class: "el-button el-button--primary el-button--large is-round", }, text: "更多", ev: { click: onExtend }, }), ] : []), // 如果存在其他temp类元素,说明当前不是第一层弹窗 ...(document.querySelectorAll(".temp").length > 0 ? [ self.createNode({ tagName: "button", obj: { class: "el-button el-button--warning el-button--large is-round", }, text: "返回", ev: { click: () => { // 移除当前弹窗 if (popupNode.parentNode) { popupNode.parentNode.removeChild(popupNode); } }, }, }), ] : []), ], }), // 右侧确认按钮 self.createNode({ tagName: "button", obj: { class: "el-button el-button--default el-button--large is-round", }, text: "确定", ev: { click: () => { if (onConfirm) onConfirm(); if (document.querySelectorAll(".temp").length > 1) { if (popupNode.parentNode) { popupNode.parentNode.removeChild(popupNode); } } else { // 否则清除所有弹窗 document.getElementById("mask").style.display = "none"; document.querySelectorAll(".temp").forEach((el) => { if (el.parentNode) el.parentNode.removeChild(el); }); } }, }, }), ], }), ], ev: { // 阻止默认的表单提交行为 submit: (e) => e.preventDefault(), // 阻止回车冒泡 keydown: (e) => { if (e.key === "Enter") { e.preventDefault(); e.stopPropagation(); } }, }, }); app.appendChild(popupNode); return popupNode; }; self.currentPopup = null; // 更新弹窗 self.updatePopup = (enableSearch, tempSettings) => { // 如果存在旧弹窗,先移除 if (self.currentPopup && self.currentPopup.parentNode) { self.currentPopup.parentNode.removeChild(self.currentPopup); } const mainSettings = self.showSettings(tempSettings); // 创建新弹窗 self.currentPopup = self.createPopUp( "设置", mainSettings.node, () => { Object.assign(settings, mainSettings.tempSettings); methods.saveData(); tip({ type: "success", message: "设置已保存", duration: 1000, }); }, 30, 40, enableSearch ? () => { const searchSettings = self.showSearchSettings(); self.createPopUp( "搜索设置", searchSettings.node, () => { Object.assign(settings, searchSettings.tempSettings); methods.saveData(); tip({ type: "success", message: "设置已保存", duration: 1000, }); }, 30, 40 ); } : null ); }; // 数值输入框及其保存按钮 self.createNumberInput = (options) => { const { value, // 初始值 min, // 最小值 max, // 最大值 step, // 步进值 style, // 样式 onSave, // 保存回调 updateValue, // 值更新回调(可选) } = options; let inputElement = null; let saveButton = null; let currentBaseValue = value; // 创建更新按钮状态的函数 const updateSaveButton = () => { if (!inputElement || !saveButton) return; const currentValue = parseInt(inputElement.value); const isDifferent = currentValue !== currentBaseValue; saveButton.style.opacity = isDifferent ? "1" : "0.5"; saveButton.style.cursor = isDifferent ? "pointer" : "not-allowed"; saveButton.disabled = !isDifferent; }; // 提供更新基准值的函数 const updateBaseValue = (newValue) => { currentBaseValue = newValue; updateSaveButton(); }; // 创建输入框 inputElement = self.createNode({ tagName: "input", obj: { class: "el-input__inner", type: "number", value: value, min: min, max: max, step: step, style: style || "width: 40%; margin-left: 2%; margin-right: 2%; height: 30px", }, ev: { input: updateSaveButton, wheel: (e) => { e.preventDefault(); const delta = e.deltaY > 0 ? -parseInt(step) : parseInt(step); const newValue = Math.max( parseInt(min), Math.min(parseInt(max), parseInt(e.target.value) + delta) ); e.target.value = newValue; updateSaveButton(); if (updateValue) updateValue(newValue); }, keydown: (e) => { if (e.key === "Enter") { e.preventDefault(); saveButton.click(); } else if (e.key === "ArrowUp" || e.key === "ArrowDown") { e.preventDefault(); const delta = e.key === "ArrowUp" ? parseInt(step) : -parseInt(step); const newValue = Math.max( parseInt(min), Math.min(parseInt(max), parseInt(e.target.value) + delta) ); e.target.value = newValue; updateSaveButton(); if (updateValue) updateValue(newValue); } }, }, }); // 创建保存按钮 saveButton = self.createNode({ tagName: "button", obj: { class: "el-button el-button--primary el-button--small", style: "opacity: 0.5; cursor: not-allowed", disabled: true, }, text: "应用", ev: { click: () => { onSave(parseInt(inputElement.value)); saveButton.style.opacity = "0.5"; saveButton.style.cursor = "not-allowed"; saveButton.disabled = true; }, }, }); return { input: inputElement, button: saveButton, updateSaveButton: updateSaveButton, updateBaseValue: updateBaseValue, }; }; // 设置 self.showSettings = (tempSettings) => { tempSettings = tempSettings ? tempSettings : JSON.parse(JSON.stringify(settings)); const intervalInput = self.createNumberInput({ value: methods.getCurrentInterval(tempSettings), min: tempSettings.mode.isGrouped ? "1000" : "100", max: tempSettings.mode.isGrouped ? "2000" : "1000", step: "25", onSave: (value) => { const mode = tempSettings.mode.isAsync ? "async" : "sync"; const type = tempSettings.mode.isGrouped ? "group" : "single"; tempSettings.interval[mode][type] = value; }, }); const settingsNode = self.createNode({ tagName: "div", obj: { style: "margin-left: 10%", }, children: [ // 抢课方式选择 self.createNode({ tagName: "div", obj: { style: "margin-bottom: 5%", }, children: [ self.createNode({ tagName: "label", text: "抢课方式:", obj: { style: "margin-right: 10%", }, }), self.createNode({ tagName: "input", obj: { type: "radio", name: "cycle-mode", id: "single-cycle", }, ev: { change: (e) => { tempSettings.mode.isCyclic = !e.target.checked; }, }, }), self.createNode({ tagName: "label", text: " 单次抢课", obj: { for: "single-cycle", style: "margin-right: 10%", }, }), self.createNode({ tagName: "input", obj: { type: "radio", name: "cycle-mode", id: "multi-cycle", }, ev: { change: (e) => { tempSettings.mode.isCyclic = e.target.checked; }, }, }), self.createNode({ tagName: "label", text: " 循环抢课", obj: { for: "multi-cycle", }, }), ], }), // 发送模式选择 self.createNode({ tagName: "div", obj: { style: "margin-bottom: 5%", }, children: [ self.createNode({ tagName: "label", text: "发送模式:", obj: { style: "margin-right: 10%", }, }), self.createNode({ tagName: "input", obj: { type: "radio", name: "send-mode", id: "sync-mode", }, ev: { change: (e) => { tempSettings.mode.isAsync = !e.target.checked; const newValue = methods.getCurrentInterval(tempSettings); intervalInput.input.value = newValue; intervalInput.updateBaseValue(newValue); // 更新基准值 }, }, }), self.createNode({ tagName: "label", text: " 同步模式", obj: { for: "sync-mode", style: "margin-right: 10%", }, }), self.createNode({ tagName: "input", obj: { type: "radio", name: "send-mode", id: "async-mode", }, ev: { change: (e) => { tempSettings.mode.isAsync = e.target.checked; const newValue = methods.getCurrentInterval(tempSettings); intervalInput.input.value = newValue; intervalInput.updateBaseValue(newValue); // 更新基准值 }, }, }), self.createNode({ tagName: "label", text: " 异步模式", obj: { for: "async-mode", }, }), ], }), // 发送方式选择 self.createNode({ tagName: "div", obj: { style: "margin-bottom: 5%", }, children: [ self.createNode({ tagName: "label", text: "发送方式:", obj: { style: "margin-right: 10%", }, }), self.createNode({ tagName: "input", obj: { type: "radio", name: "group-mode", id: "single-send", }, ev: { change: (e) => { tempSettings.mode.isGrouped = !e.target.checked; const newValue = methods.getCurrentInterval(tempSettings); intervalInput.input.value = newValue; intervalInput.updateBaseValue(newValue); // 更新基准值 }, }, }), self.createNode({ tagName: "label", text: " 单个发送", obj: { for: "single-send", style: "margin-right: 10%", }, }), self.createNode({ tagName: "input", obj: { type: "radio", name: "group-mode", id: "group-send", }, ev: { change: (e) => { tempSettings.mode.isGrouped = e.target.checked; const newValue = methods.getCurrentInterval(tempSettings); intervalInput.input.value = newValue; intervalInput.updateBaseValue(newValue); // 更新基准值 }, }, }), self.createNode({ tagName: "label", text: " 分组发送", obj: { for: "group-send", }, }), ], }), // 时间间隔设置 self.createNode({ tagName: "div", obj: { style: "display: flex; align-items: center", }, children: [ self.createNode({ tagName: "label", text: "时间间隔(ms):", obj: { style: "margin-right: 10px", }, }), intervalInput.input, intervalInput.button, ], }), // 搜索功能启用开关 self.createNode({ tagName: "div", obj: { style: "margin-top: 5%", }, children: [ self.createNode({ tagName: "label", text: "启用搜索功能:", obj: { style: "margin-right: 5%", }, }), self.createNode({ tagName: "input", obj: { type: "checkbox", id: "enable-search", }, ev: { change: (e) => { tempSettings.mode.enableSearch = e.target.checked; self.updatePopup(e.target.checked, tempSettings); }, }, }), ], }), ], }); // 创建完成后进行初始化 setTimeout(() => { // 获取所有需要初始化的单选按钮 const singleCycleInput = settingsNode.querySelector("#single-cycle"); const multiCycleInput = settingsNode.querySelector("#multi-cycle"); const syncModeInput = settingsNode.querySelector("#sync-mode"); const asyncModeInput = settingsNode.querySelector("#async-mode"); const singleSendInput = settingsNode.querySelector("#single-send"); const groupSendInput = settingsNode.querySelector("#group-send"); const enableSearchInput = settingsNode.querySelector("#enable-search"); // 根据设置初始化抢课方式 if (tempSettings.mode.isCyclic) { multiCycleInput.checked = true; } else { singleCycleInput.checked = true; } // 根据设置初始化发送模式 if (tempSettings.mode.isAsync) { asyncModeInput.checked = true; } else { syncModeInput.checked = true; } // 根据设置初始化发送方式 if (tempSettings.mode.isGrouped) { groupSendInput.checked = true; } else { singleSendInput.checked = true; } // 根据设置初始化搜索功能开关 if (tempSettings.mode.enableSearch) { enableSearchInput.checked = true; } }, 0); return { node: settingsNode, tempSettings: tempSettings, }; }; // 搜索设置 self.showSearchSettings = () => { // 创建临时设置对象和引用变量 const tempSettings = JSON.parse(JSON.stringify(settings)); const pageSizeInput = self.createNumberInput({ value: tempSettings.search.pageSize, min: "10", max: "100", step: "10", onSave: (value) => { tempSettings.search.pageSize = value; }, }); const pageDelayInput = self.createNumberInput({ value: tempSettings.search.pageDelay, min: "100", max: "2000", step: "100", onSave: (value) => { tempSettings.search.pageDelay = value; }, }); const settingsNode = self.createNode({ tagName: "div", obj: { style: "margin: 10%" }, children: [ // 每页数量设置 self.createNode({ tagName: "div", obj: { style: "margin-bottom: 5%" }, children: [ self.createNode({ tagName: "label", text: "每页数量:", obj: { style: "margin-right: 10%" }, }), pageSizeInput.input, pageSizeInput.button, ], }), // 翻页延迟设置 self.createNode({ tagName: "div", obj: { style: "margin-bottom: 5%" }, children: [ self.createNode({ tagName: "label", text: "翻页延迟:", obj: { style: "margin-right: 10%" }, }), pageDelayInput.input, pageDelayInput.button, ], }), ], }); return { node: settingsNode, tempSettings: tempSettings, }; }; // 生成课程详情信息 self.showCourseDetails = (course) => { return self.createNode({ tagName: "div", obj: { style: `margin:5%`, }, children: [ self.createNode({ tagName: "table", obj: { width: "80%", border: "1", style: ` background-color: rgba(0,0,0,0); color: black; margin: 0 auto; `, }, children: [ // 表头 self.createNode({ tagName: "tr", obj: { style: ` height: 30px; background-color: #255e95; color: lightblue; `, }, HTML: ` <th style="text-align:center;width: 30%">属性</th> <th style="text-align:center;width: 70%">值</th> `, }), // 课程号和课程名 self.createNode({ tagName: "tr", obj: { style: `height: 30px`, }, children: [ self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: "课程信息", }), self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: `${course.courseName}`, }), ], }), // 学院和教师 self.createNode({ tagName: "tr", obj: { style: `height: 30px`, }, children: [ self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: "开课单位/教师", }), self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: `${course.department} ${course.teacherName}`, }), ], }), // 授课地点 self.createNode({ tagName: "tr", obj: { style: `height: 30px`, }, children: [ self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: "授课地点", }), self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: course.location || "待定", }), ], }), // 课程性质和类别 self.createNode({ tagName: "tr", obj: { style: `height: 30px`, }, children: [ self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: "课程属性", }), self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: `${course.courseNature} ${course.courseCategory}`, }), ], }), // 选课人数 self.createNode({ tagName: "tr", obj: { style: `height: 30px`, }, children: [ self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: "选课人数", }), self.createNode({ tagName: "td", obj: { style: `text-align: center` }, text: `${course.selectedCount}/${course.totalCapacity}`, }), ], }), ], }), ], }); }; //生成抢课按钮 self.addEnrollButton = () => { // 监听课程块的点击事件 document.addEventListener("click", function (event) { const target = event.target; const trElement = target.closest("tr.el-table__row"); if (trElement && trElement.classList.contains("expanded")) { setTimeout(() => { const expandedRow = trElement.nextElementSibling; const expandedCell = expandedRow.querySelector( "td.el-table__expanded-cell" ); if (!expandedCell) { console.error("未找到 expandedCell"); return; } // 获取课程编码 const courseCode = trElement.querySelector("td span").innerText; // 获取课程名称 const courseName = trElement.querySelector( "td:nth-child(2) span" ).innerText; // 获取 expandedCell 内的所有“选择”按钮 const selectButtons = Array.from( expandedCell.querySelectorAll( "button.el-button--primary.el-button--mini.is-round span" ) ).filter((span) => span.innerText.includes("选择")); selectButtons.forEach((selectButton) => { // 创建“添加”按钮 const addButton = document.createElement("button"); addButton.className = "el-button el-button--primary el-button--mini is-round add-course-button"; addButton.innerHTML = "<span>添加</span>"; // 存储课程编码到按钮属性中 addButton.setAttribute("data-course-code", courseCode); addButton.setAttribute("data-course-name", courseName); // 设置按钮的禁用状态 if (isRunning) { addButton.disabled = true; addButton.style.cursor = "not-allowed"; addButton.style.opacity = "0.5"; } // 在“选择”按钮后插入“添加”按钮 selectButton.parentElement.parentElement.appendChild(addButton); // 添加点击事件 addButton.addEventListener("click", function () { // 获取课程班编号 const classRow = selectButton.closest(".el-card__body"); if (!classRow) { console.error("未找到 classRow"); return; } const sequenceInfo = classRow .querySelector(".one-row span") .innerText.replace("[", ""); // 获取存储的课程编码 const storedCourseCode = addButton.getAttribute("data-course-code"); // 拼接课程编码与课程班编号 const courseString = storedCourseCode + sequenceInfo; // 使用 addSingleCourse 函数添加课程 methods.addSingleCourse(courseString); }); }); }, 100); // 增加延迟时间以确保详情块已插入 } }); }; })((window.Components = window.Components || {})); let methods = { // 初始化函数 async init() { // 初始化设置为默认值的深拷贝 settings = JSON.parse(JSON.stringify(defaultSettings)); let raw = JSON.parse(localStorage.getItem("july")); if (raw) { // 循环检查 sessionStorage.token 直到其不为 undefined while (typeof sessionStorage.token === "undefined") { await new Promise((resolve) => setTimeout(resolve, 50)); } // 递归合并设置 const mergeSettings = (target, source) => { Object.keys(target).forEach((key) => { if (source[key] !== undefined) { if ( typeof target[key] === "object" && !Array.isArray(target[key]) ) { mergeSettings(target[key], source[key]); } else { target[key] = source[key]; } } }); }; if (raw.settings) { mergeSettings(settings, raw.settings); } // 检查token匹配 if (settings.token === sessionStorage.token) { enrollDict = raw.enrollDict; } else if (raw.enrollDict && JSON.stringify(raw.enrollDict) !== "{}") { const courseCodes = Object.keys(raw.enrollDict); const inputBox = document.getElementById("input-box"); inputBox.value = courseCodes.join(" "); if (settings.mode.enableSearch) { tip({ type: "warning", message: "token失效,尝试重新添加课程", duration: 2000, }); enrollDict = {}; // 课程类型中文映射 const typeNames = { TJKC: "推荐课程", FANKC: "方案内课程", FAWKC: "方案外课程", TYKC: "体育项目", XGKC: "通选课", }; // 尝试在不同类型中查找课程 const types = ["TJKC", "FANKC", "FAWKC", "TYKC", "XGKC"]; let remainingCodes = courseCodes; const pageSize = settings.search.pageSize; for (let type of types) { if (!remainingCodes.length) break; let pageNumber = 1; while (true) { if (!remainingCodes.length) break; await new Promise((resolve) => setTimeout(resolve, settings.search.pageDelay) ); const { courseList, total } = await methods.searchCourse( type, pageNumber, pageSize ); if (!courseList.length) break; // 每页获取后立即尝试添加 if (courseList.length > 0) { remainingCodes = methods.addEnrollDict( remainingCodes.join(" "), type, courseList, false ); } inputBox.value = remainingCodes.join(" "); tip({ type: "success", message: `已获取 ${typeNames[type]} 第 ${pageNumber} 页,剩余未找到课程:${remainingCodes.length}门`, duration: 2000, }); if (pageNumber * pageSize >= total) break; pageNumber++; } } if (remainingCodes.length > 0) { tip({ type: "warning", message: `以下课程未找到:${remainingCodes}`, duration: 2000, }); } settings.savedCourseCodes = remainingCodes.join(" "); } else { // 未启用搜索功能时的处理逻辑 const codeStr = courseCodes.join(" "); inputBox.value = codeStr; settings.savedCourseCodes = codeStr; tip({ type: "warning", message: "登录信息发生变动,已清空抢课列表", duration: 1000, }); enrollDict = {}; } } } // 更新token settings.token = sessionStorage.token; // 初始化状态 isRunning = false; shouldStop = false; // 更新UI methods.updateUIState(); window.Components.reloadList(); // 保存清理后的数据 methods.saveData(); }, // 保存数据到本地存储 saveData() { localStorage.setItem("july", JSON.stringify({ enrollDict, settings })); }, //处理按钮拖动与点击 drag(e, node) { let is_move = false; let x = e.pageX - node.offsetLeft; let y = e.pageY - node.offsetTop; document.onmousemove = function (e) { node.style.left = e.pageX - x + "px"; node.style.top = e.pageY - y + "px"; is_move = true; }; document.onmouseup = function () { document.onmousemove = document.onmouseup = null; if (!is_move) { let panel = document.getElementById("panel"); panel.style.display === "block" ? (panel.style.display = "none") : (panel.style.display = "block"); } is_move = false; }; }, // 更新UI状态(禁用/启用按钮) updateUIState() { const inputBox = document.getElementById("input-box"); const settingsStopButton = document.getElementById( "settings-stop-button" ); const listWrap = document.getElementById("list-wrap"); if (isRunning) { // 禁用输入和编辑功能 inputBox.disabled = true; inputBox.style.cursor = "not-allowed"; inputBox.style.opacity = "0.5"; // 更新设置/停止按钮为停止状态 settingsStopButton.className = "el-button el-button--danger el-button--small is-round"; settingsStopButton.textContent = "停止抢课"; settingsStopButton.style.cursor = "pointer"; settingsStopButton.style.opacity = "1"; // 禁用表格中的删除键 listWrap.querySelectorAll("button.delete-button").forEach((button) => { button.disabled = true; button.style.cursor = "not-allowed"; button.style.opacity = "0.5"; }); // 禁用添加课程按钮 document .querySelectorAll("button.add-course-button") .forEach((button) => { button.disabled = true; button.style.cursor = "not-allowed"; button.style.opacity = "0.5"; }); } else { // 启用输入和编辑功能 inputBox.disabled = false; inputBox.style.cursor = "auto"; inputBox.style.opacity = "1"; // 更新设置/停止按钮为设置状态 settingsStopButton.className = "el-button el-button--info el-button--small is-round"; settingsStopButton.textContent = "扩展设置"; settingsStopButton.style.cursor = "pointer"; settingsStopButton.style.opacity = "1"; // 启用表格中的删除键 listWrap.querySelectorAll("button.delete-button").forEach((button) => { button.disabled = false; button.style.cursor = "pointer"; button.style.opacity = "1"; }); // 启用添加课程按钮 document .querySelectorAll("button.add-course-button") .forEach((button) => { button.disabled = false; button.style.cursor = "pointer"; button.style.opacity = "1"; }); } }, // 处理输入框事件 enter(e) { if (e.key === "Enter") { let node = document.getElementById("input-box"); let codeArray = node.value.toUpperCase().split(" "); let failedCodes = methods.addEnrollDict(codeArray.join(" ")); node.value = failedCodes.join(" "); // 将失败的课程代码替换到输入框中 } }, // 插入课程 insertCourse(code, currentCourseList, currentType) { let courseCode = code.substring(0, 8); let teacherCode = code.substring(8); let courseFlag = false, teacherFlag = false; const createCourseInfo = (course, teacher) => ({ // 选课信息 courseBatch: grablessonsVue.lcParam.currentBatch.code, classID: teacher.JXBID, courseType: currentType, secretVal: teacher.secretVal, // 更多信息 courseName: course.KCM, teacherName: teacher.SKJS, department: teacher.KKDW, // 开课单位(学院) location: teacher.YPSJDD, // 授课地点 selectedCount: teacher.numberOfSelected, // 已选人数 totalCapacity: teacher.classCapacity, // 总容量 courseNature: teacher.KCXZ, // 课程性质 courseCategory: teacher.KCLB, // 课程类别 }); for (let course of currentCourseList) { // 检查课程是否存在 if (course.KCH === courseCode) { courseFlag = true; // 检查教师是否存在 if (currentType !== "XGKC") { for (let teacher of course.tcList) { if (teacher.KXH === teacherCode) { enrollDict[code] = createCourseInfo(course, teacher); teacherFlag = true; } } } else { if (course.KXH === teacherCode) { enrollDict[code] = createCourseInfo(course, course); teacherFlag = true; } } } } return { courseFlag, teacherFlag }; }, // 处理课程插入逻辑 handleCourseInsertion(code, currentCourseList, currentType) { let { courseFlag, teacherFlag } = methods.insertCourse( code, currentCourseList, currentType ); if (!courseFlag) { return { success: false, message: "没有查找到该课程,请检查课程号", type: "error", }; } else if (!teacherFlag) { console.log("无效的教师号: ", code.substring(8)); return { success: false, message: "没有查找到该教师,请检查教师号", type: "error", }; } else { const course = enrollDict[code]; return { success: true, message: `成功添加 ${course.teacherName} 的 ${course.courseName}`, type: "success", }; } }, // 添加课程到抢课列表 addEnrollDict( str, currentType = null, currentCourseList = null, showTip = true ) { if (!str) return []; // 如果没有传入参数,使用默认值 currentType = currentType || grablessonsVue.teachingClassType; currentCourseList = currentCourseList || grablessonsVue.courseList; let codeArray = str.split(" "); let failedCodes = []; for (let i = 0; i < codeArray.length; i++) { let code = codeArray[i]; if (!code) continue; const course = enrollDict[code]; if (course) { if (showTip) { tip({ type: "error", message: `${course.teacherName} 的 ${course.courseName} 已添加`, duration: 1000, }); } continue; } let result = methods.handleCourseInsertion( code, currentCourseList, currentType ); if (!result.success) { failedCodes.push(code); } if (showTip) { tip({ type: result.type, message: result.message, duration: 1000, }); } } methods.saveData(); window.Components.reloadList(); return failedCodes; }, // 通过页面按钮添加课程 addSingleCourse(code) { if (!code) return; let currentType = grablessonsVue.teachingClassType; let currentCourseList = grablessonsVue.courseList; const course = enrollDict[code]; if (course) { tip({ type: "error", message: `${course.teacherName} 的 ${course.courseName} 已添加`, duration: 1000, }); return; } let result = methods.handleCourseInsertion( code, currentCourseList, currentType ); tip({ type: result.type, message: result.message, duration: 1000, }); methods.saveData(); window.Components.reloadList(); }, // 获取当前应用的间隔时间 getCurrentInterval(settingsObj) { const mode = settingsObj.mode.isAsync ? "async" : "sync"; const type = settingsObj.mode.isGrouped ? "group" : "single"; return settingsObj.interval[mode][type]; }, // 一键抢课 async enroll() { let key_list = Object.keys(enrollDict).filter( (key) => enrollDict[key].courseBatch === grablessonsVue.lcParam.currentBatch.code ); if (!key_list.length) { tip({ type: "warning", message: "抢课列表为空", duration: 1000, }); isRunning = false; methods.updateUIState(); return; } let index = 0; // 发送单个抢课请求并处理响应 const sendEnrollRequest = async (key) => { const course = enrollDict[key]; const enrollResponse = await request({ url: "/elective/clazz/add", method: "POST", headers: { batchId: course.courseBatch, "content-type": "application/x-www-form-urlencoded", }, data: Qs.stringify({ clazzType: course.courseType, clazzId: course.classID, secretVal: course.secretVal, }), }); let type = enrollResponse.data.code === 200 ? "success" : "warning"; tip({ type, message: enrollResponse.data.code === 200 ? `已成功添加 ${course.teacherName} 的 ${course.courseName} 到选课队列` : `${course.teacherName} 的 ${course.courseName}: ${enrollResponse.data.msg}`, duration: 1000, }); if (enrollResponse.data.code === 200) { delete enrollDict[key]; methods.saveData(); window.Components.reloadList(); return true; } else if (enrollResponse.data.code === 301) { const confirmResponse = await request({ url: "/elective/clazz/add", method: "POST", headers: { batchId: course.courseBatch, "content-type": "application/x-www-form-urlencoded", }, data: Qs.stringify({ clazzType: course.courseType, clazzId: course.courseCode, secretVal: course.secretVal, isConfirm: 1, }), }); if (confirmResponse.data.code === 200) { tip({ type: "success", message: `已成功添加 ${course.teacherName} 的 ${course.courseName} 到选课队列`, duration: 1000, }); delete enrollDict[key]; methods.saveData(); window.Components.reloadList(); return true; } } return false; }; const doEnroll = async () => { if (shouldStop) { isRunning = false; methods.updateUIState(); return; } if (index >= key_list.length) { if (settings.mode.isCyclic && Object.keys(enrollDict).length) { key_list = Object.keys(enrollDict).filter( (key) => enrollDict[key].courseBatch === grablessonsVue.lcParam.currentBatch.code ); if (key_list.length) { index = 0; } else { isRunning = false; methods.updateUIState(); return; } } else { isRunning = false; methods.updateUIState(); return; } } if (settings.mode.isGrouped) { // 分组发送模式:每组3个请求 const groupKeys = key_list.slice(index, index + 3); index += 3; if (settings.mode.isAsync) { // 异步模式:同时发送所有请求 groupKeys.forEach((key) => sendEnrollRequest(key)); } else { // 同步模式:等待所有请求完成 await Promise.all(groupKeys.map((key) => sendEnrollRequest(key))); } } else { // 单个发送模式 const key = key_list[index++]; if (!settings.mode.isAsync) { await sendEnrollRequest(key); } else { sendEnrollRequest(key); } } // 等待间隔后发起下一次请求 await new Promise((resolve) => setTimeout(resolve, methods.getCurrentInterval(settings)) ); doEnroll(); }; // 开始执行 doEnroll(); }, // 停止抢课 async stopEnrolling() { shouldStop = true; while (isRunning) { await new Promise((resolve) => setTimeout(resolve, 50)); } await new Promise((resolve) => setTimeout(resolve, methods.getCurrentInterval(settings)) ); shouldStop = false; methods.updateUIState(); }, // 搜索课程 async searchCourse(type, pageNumber, pageSize) { const params = { teachingClassType: type, pageNumber: pageNumber, pageSize: pageSize, orderBy: "", campus: grablessonsVue.currentCampus.code, }; try { const response = await request.post("/elective/clazz/list", params); const { data } = response; if (data && data.code === 200) { return { courseList: data.data.rows, total: data.data.total, }; } } catch (error) { console.error("搜索课程失败:", error); tip({ type: "error", message: "搜索课程失败", duration: 1000, }); } return { courseList: [], total: 0 }; }, }; window.Components.mount(); methods.init(); })();