东南大学抢课助手修改版

听说你抢不到课

// ==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();
})();