diff --git a/README.md b/README.md
index 2bdf64c..6dd0a20 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,8 @@ https://www.douyin.com/video/7651581603861720441
### 🤝 换课功能
- **情境**:如果你 pre 课A 但是又想有 课B 保底就想着能不能骑驴找马,在 课A 有空的时候把 课B 退了之后再去抢
- **实现**:这就是换课功能存在的意义,当你输入目标课程的时候下方有一个可选字段 “替换课程” ,那么就会帮你自动监听并实现上面的功能
-- **⚠️注意**:由于抢 课A 前需要先把 课B 退了,所以有 课B 退了之后 课A 没空位的风险,一般是对方和你一样用了本脚本
+- **多门替换**:替换课程支持填写多个课程号(逗号分隔),脚本会**依次退选**所有替换课程后再抢新课。适合需要同时腾出多个时间段的场景(如退掉两门课才能选上目标课)
+- **⚠️注意**:由于抢 课A 前需要先把替换课程全部退了,若退选后目标课已无空位则等待下次机会;任意一门替换课退选失败时本次抢课中止
### ⏰ 定时功能
- **定时开抢** - 设置精确的开抢时间
@@ -129,7 +130,8 @@ javascript:(function(){var script=document.createElement('script');script.src='
2. 设置优先级(数字越小优先级越高)
3. 可选:设置时间过滤器(如:`星期一,第1-2节`)
4. 可选:设置教师过滤器(如:`叶利群,讲师`)
-5. 点击"➕ 添加课程"按钮
+5. 可选:设置替换课程(如:`23306047` 或多门 `23306047,23306048`,逗号分隔,选中新课前依次退选)
+6. 点击"➕ 添加课程"按钮
**方式二:修改代码配置**
@@ -147,6 +149,16 @@ const TARGET_COURSES = [
timeFilter: ['星期一', '第1-2节'],
teacherFilter: ['叶利群', '讲师']
},
+ {
+ code: '23306048',
+ priority: 3,
+ replaceCodes: ['23306047'] // 选上此课前先退选一门
+ },
+ {
+ code: '23306049',
+ priority: 4,
+ replaceCodes: ['23306047', '23306050'] // 选上此课前依次退选两门
+ },
];
```
diff --git a/courseGrabber.js b/courseGrabber.js
index 3d2da3a..952de3e 100644
--- a/courseGrabber.js
+++ b/courseGrabber.js
@@ -1702,24 +1702,36 @@
// 获取课程配置
const courseConfig = TARGET_COURSES.find((c) => c.code === courseCode);
- // 如果配置了替换课程,先执行退选
- if (courseConfig && courseConfig.replaceCode) {
+ // 如果配置了替换课程,先执行退选(支持多门)
+ const _replaceCodes = courseConfig && courseConfig.replaceCodes && courseConfig.replaceCodes.length > 0
+ ? courseConfig.replaceCodes
+ : null;
+ if (_replaceCodes) {
+ const _codesList = _replaceCodes.join(", ");
log(
- `🔄 检测到需要替换课程 ${courseConfig.replaceCode},立即执行退选...`,
+ `🔄 检测到需要替换课程 ${_codesList},依次执行退选...`,
"warning",
courseCode,
);
addUILog &&
addUILog(
"warning",
- `[${courseCode}] 🔄 发现空位!开始退选 ${courseConfig.replaceCode}`,
+ `[${courseCode}] 🔄 发现空位!开始退选 ${_codesList}`,
);
- // 异步执行退选,然后选课
- dropCourse(courseConfig.replaceCode).then((dropSuccess) => {
+ // 依次退选所有替换课程,全部成功后再选新课
+ const dropSequentially = (codes) => {
+ if (codes.length === 0) return Promise.resolve(true);
+ return dropCourse(codes[0]).then((ok) => {
+ if (!ok) return Promise.resolve(false);
+ return dropSequentially(codes.slice(1));
+ });
+ };
+
+ dropSequentially(_replaceCodes).then((dropSuccess) => {
if (dropSuccess) {
log(
- `✅ 退选成功,立即选择新课程 ${courseCode}`,
+ `✅ 全部退选成功,立即选择新课程 ${courseCode}`,
"success",
courseCode,
);
@@ -2783,8 +2795,8 @@
支持教师姓名或职称,满足任意一个即可
替换课程 (可选)
-
- 选中新课程前,先退选此课程(用于换课)
+
+ 多门课用逗号分隔,选中新课前依次退选这些课
@@ -2918,7 +2930,7 @@
const timeFilterEl = document.getElementById("cg-time-filter");
const teacherFilterEl = document.getElementById("cg-teacher-filter");
- const replaceCode = (replaceCodeEl ? replaceCodeEl.value : "").trim();
+ const replaceCodeInput = (replaceCodeEl ? replaceCodeEl.value : "").trim();
const timeFilterInput = (timeFilterEl ? timeFilterEl.value : "").trim();
const teacherFilterInput = (
teacherFilterEl ? teacherFilterEl.value : ""
@@ -2926,7 +2938,8 @@
// 构造最终要推入的课程对象,避免作用域或外部修改影响
const finalCourse = { code: code, priority: priority };
- if (replaceCode) finalCourse.replaceCode = replaceCode;
+ const finalReplaceCodes = safeParseFilterInput(replaceCodeInput);
+ if (finalReplaceCodes.length > 0) finalCourse.replaceCodes = finalReplaceCodes;
// 使用安全的解析函数处理过滤器输入(避免被篡改的 Array.prototype.filter)
const finalTimeFilter = safeParseFilterInput(timeFilterInput);
@@ -2952,8 +2965,8 @@
updateCourseList();
let logMsg = `已添加课程: ${code} (优先级: ${priority})`;
- if (finalCourse.replaceCode)
- logMsg += ` [替换: ${finalCourse.replaceCode}]`;
+ if (finalCourse.replaceCodes && finalCourse.replaceCodes.length > 0)
+ logMsg += ` [替换: ${finalCourse.replaceCodes.join(", ")}]`;
if (finalCourse.timeFilter && finalCourse.timeFilter.length > 0) {
logMsg += ` [时间过滤: ${finalCourse.timeFilter.join(", ")}]`;
}
@@ -3035,13 +3048,13 @@
list.innerHTML = TARGET_COURSES.map((course, index) => {
const hasConfig =
- course.replaceCode || course.timeFilter || course.teacherFilter;
+ (course.replaceCodes && course.replaceCodes.length > 0) || course.timeFilter || course.teacherFilter;
let filterHTML = "";
if (hasConfig) {
filterHTML = '';
- if (course.replaceCode) {
- filterHTML += `
🔄 替换:${course.replaceCode}
`;
+ if (course.replaceCodes && course.replaceCodes.length > 0) {
+ filterHTML += `
🔄 替换:${course.replaceCodes.join(", ")}
`;
}
if (course.timeFilter) {
filterHTML += `
⏰ 时间:${course.timeFilter.join(", ")}
`;
@@ -3084,12 +3097,12 @@
window.editCourseUI = (index) => {
const course = TARGET_COURSES[index];
- const replaceCode = prompt(
- `编辑课程 ${course.code} 的替换课程\n\n输入要替换的课程号,留空表示不替换\n例如: 23306047`,
- course.replaceCode || "",
+ const replaceCodeInput = prompt(
+ `编辑课程 ${course.code} 的替换课程\n\n多门课用逗号分隔,留空表示不替换\n例如: 23306047,23306048`,
+ course.replaceCodes ? course.replaceCodes.join(",") : "",
);
- if (replaceCode === null) return; // 用户取消
+ if (replaceCodeInput === null) return; // 用户取消
const timeFilter = prompt(
`编辑课程 ${course.code} 的时间过滤器\n\n多个关键词用逗号分隔,留空表示不过滤\n例如: 星期一,第1-2节`,
@@ -3106,10 +3119,11 @@
if (teacherFilter === null) return; // 用户取消
// 更新课程配置
- if (replaceCode.trim()) {
- course.replaceCode = replaceCode.trim();
+ const parsedReplaceCodes = safeParseFilterInput(replaceCodeInput);
+ if (parsedReplaceCodes.length > 0) {
+ course.replaceCodes = parsedReplaceCodes;
} else {
- delete course.replaceCode;
+ delete course.replaceCodes;
}
// 使用安全的解析函数处理过滤器输入(避免被篡改的 Array.prototype.filter)