UCAS Class Enrollment Assistant

这是一个方便抢课界面操作的辅助工具。包括的功能有:1. 🚪直达战场: 进入选课系统后,自动跳转到选课页面。(如需查看通知公告 需要临时把本工具禁用) 2. 🚀一键跳转: 点击小火箭,想去哪里点哪里!更有高亮与自动滚动,帮助快速定位课程。 3. ✔快速提交: 不想滚到底部才能提交选课?验证码和提交选课按钮直接整合到面板! 3.1. 选课、学位课复选框添加到面板中; 3.2. 修复了原版选课系统点击"切换验证码"没反应的bug,现在可以点击验证码图片更新没有加载出来的验证码了; 3.3. 提交选课时自动跳过"确认提交吗"对话框。 4. 🎨标注课程状态: 绿色表示已选上的课程,红色表示已满员的课程。(只有进入选课页面才会更新课程是否已满员)

  1. // ==UserScript==
  2. // @name UCAS Class Enrollment Assistant
  3. // @version 1.7.2
  4. // @description 这是一个方便抢课界面操作的辅助工具。包括的功能有:1. 🚪直达战场: 进入选课系统后,自动跳转到选课页面。(如需查看通知公告 需要临时把本工具禁用) 2. 🚀一键跳转: 点击小火箭,想去哪里点哪里!更有高亮与自动滚动,帮助快速定位课程。 3. ✔快速提交: 不想滚到底部才能提交选课?验证码和提交选课按钮直接整合到面板! 3.1. 选课、学位课复选框添加到面板中; 3.2. 修复了原版选课系统点击"切换验证码"没反应的bug,现在可以点击验证码图片更新没有加载出来的验证码了; 3.3. 提交选课时自动跳过"确认提交吗"对话框。 4. 🎨标注课程状态: 绿色表示已选上的课程,红色表示已满员的课程。(只有进入选课页面才会更新课程是否已满员)
  5. // @author bazingaW
  6. // @namespace https://github.com/bazingaW/ucas_class_enrollment_assistant
  7. // @match http*://jwxk.ucas.ac.cn/*
  8. // @match http*://jwxkts2.ucas.ac.cn/*
  9. // @icon https://sep.ucas.ac.cn/favicon.ico
  10. // @grant GM_setClipboard
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @require https://cdn.jsdelivr.net/npm/draggable@4.2.0/src/draggable.js
  14. // @require https://cdn.jsdelivr.net/npm/jquery-throttle-debounce@1.0.0/jquery.ba-throttle-debounce.min.js
  15. // @run-at document-end
  16. // @license MIT
  17. // ==/UserScript==
  18.  
  19. var config = {
  20. 'wishList': {
  21. // 选课系统中学院名称的*前两个字*,具体可参考下面的DeptIdMap
  22. '马克': [
  23. // 一个课程一个花括号
  24. {
  25. 'name': '新时代中国特色社会主义理论与实践研究',
  26. 'wishes': [ // 可以为空列表
  27. // 每个班用一个花括号,notes里可以随意填写,给自己看的。
  28. {
  29. 'courseid': '030500MGB001H-10',
  30. 'notes': '2-10周 周四(9-12)'
  31. },
  32. {
  33. 'courseid': '030500MGB001H-34',
  34. 'notes': '11-18周 周四(9-12)'
  35. }
  36. ]
  37. },
  38. {
  39. 'name': '自然辩证法概论',
  40. 'wishes': [
  41. {
  42. 'courseid': '010108MGB001H-16',
  43. 'notes': '周三(9-12)'
  44. },
  45. {
  46. 'courseid': '010108MGB001H-30',
  47. 'notes': '周六(9-12)'
  48. },
  49. {
  50. 'courseid': '010108MGB001H-31',
  51. 'notes': '周六(9-12)'
  52. }
  53. ]
  54. },
  55. ],
  56. },
  57. };
  58.  
  59.  
  60. const DeptIdMap = {
  61. '数学': "id_910", '物理': "id_911", '天文': "id_957", '化学': "id_912", '材料': "id_928",
  62. '生命': "id_913", '地球': "id_914", '资环': "id_921", '计算': "id_951", '电子': "id_952",
  63. '工程': "id_958", '经管': "id_917", '公管': "id_945", '人文': "id_927", '马克': "id_964",
  64. '外语': "id_915", '中丹': "id_954", '国际': "id_955", '存济': "id_959", '体育': "id_946",
  65. '微电': "id_961", '未来': "id_962", '网络': "id_963", '心理': "id_968", '人工': "id_969",
  66. '纳米': "id_970", '艺术': "id_971", '光电': "id_972", '创新': "id_967", '核学': "id_973",
  67. '现代': "id_974", '化学': "id_975", '海洋': "id_976", '航空': "id_977", '杭州': "id_979",
  68. '南京': "id_985", '应急': "id_987",
  69. };
  70.  
  71. // 设置样式
  72. const mycss = `
  73. .transp{
  74. background:transparent;
  75. border-width:0;
  76. outline:none;
  77. }
  78. .notes{
  79. }
  80. .nowrap{
  81. white-space: nowrap;
  82. }
  83. .bgabtn.jump{
  84. background:transparent;
  85. border-width:0;
  86. outline:none;
  87. padding: 0;
  88. margin: 0;
  89. }
  90. .bgabtn.dept{
  91. border-width: 1px;
  92. padding: 2px;
  93. margin: 0;
  94. margin-left: 1px;
  95. }
  96. .bgabtn.dept.checked{
  97. background-color: darkgray;
  98. }
  99. .bgabtn.course{
  100. max-width: 150px;
  101. border-width: 1px;
  102. padding: 1px;
  103. margin-left: 5px;
  104. margin-right: 5px;
  105. margin-top: 5px;
  106. margin-bottom: 5px;
  107. }
  108. .bgabtn.courseid{
  109. border-width: 1px;
  110. padding: 2px;
  111. margin-left: 5px;
  112. margin-right: 5px;
  113. margin-top: 5px;
  114. margin-bottom: 5px;
  115. }
  116. .bgabtn.highlight{
  117. background-color: yellow;
  118. }
  119. .bgabtn:active{
  120. background-color: gray;
  121. }
  122. .bgabtn.selected{
  123. color: greenyellow !important;
  124. background-color: darkgray;
  125. }
  126. .bgabtn.full{
  127. color: red;
  128. }
  129. #divHeader{
  130. cursor:move;
  131. }
  132. `
  133. var sty = document.createElement("style");
  134. sty.type = "text/css";
  135. sty.appendChild(document.createTextNode(mycss));
  136. document.body.appendChild(sty);
  137.  
  138. var divCourseWish; // ui界面按钮部分
  139. var alreadyHighlighted;
  140. function prefix (...data) {
  141. return ['[抢课辅助]', ...data];
  142. }
  143.  
  144. function createElement(element, attribute, inner) {
  145. if (typeof(element) === "undefined") {
  146. return false;
  147. }
  148. if (typeof(inner) === "undefined") {
  149. inner = "";
  150. }
  151. var el = document.createElement(element);
  152. if (typeof(attribute) === 'object') {
  153. for (var key in attribute) {
  154. el.setAttribute(key, attribute[key]);
  155. }
  156. }
  157. if (!Array.isArray(inner)) {
  158. inner = [inner];
  159. }
  160. for (var k = 0; k < inner.length; k++) {
  161. if (inner[k].tagName) {
  162. el.appendChild(inner[k]);
  163. } else {
  164. el.appendChild(document.createTextNode(inner[k]));
  165. }
  166. }
  167. return el;
  168. }
  169.  
  170.  
  171. function drawPanel (page) {
  172. let divHeader = createElement(
  173. 'div',
  174. { id: "divHeader", style: "min-width: 150px; font-size:20px;font-weight: bold;text-align: center;position: fixed;width: 100%;height: 25px;border-bottom: 1px solid;" },
  175. '待选课程'
  176. );
  177.  
  178. divCourseWish = createElement(
  179. 'div',
  180. { id: "divCourseWish", style: "margin-top: 25px; max-height: 300px; overflow-y: auto;" }
  181. );
  182. let table = createElement('table', { id: "courseWish", border: "1", style: "font-size: 14px;" });
  183. let tbody = createElement('tbody');
  184. divCourseWish.append(table);
  185. table.appendChild(tbody);
  186.  
  187. let divAppendix = createElement('div', { id: "divAppendix", style: "margin: 5px; max-height: 300px; overflow-y: auto;" });
  188.  
  189. let divDrag = createElement('div', { draggable:"true", id:"divDrag", style:"bottom: 0; width:100%; height:5px; background-color:#999; cursor:n-resize;" });
  190.  
  191. let panel = createElement(
  192. 'div',
  193. { id: 'bgapanel', style: "border: 1px solid; width: fit-content; position: fixed; top: 65px; right: 0; z-index: 99999; background-color: rgba(220,221,192,0.8); overflow-x: auto;" },
  194. [divHeader, divCourseWish, divAppendix, divDrag]
  195. );
  196. document.body.appendChild(panel);
  197.  
  198. let isCourseSelection = page == 'selectCourse' || page == 'debug'; // 进入选课页面
  199. let isMain = page == 'main'; // 进入筛选学院页面
  200. let wishList = config.wishList; // 待选课程数据
  201. let bgaBtnId = 1; // 设置bgaBtn 的id编号,每次加1
  202. let chks_course=[]; // 选课复选框保存,用于统一设置事件监听器
  203. let chks_deg = []; // 学位课复选框保存,用于统一设置事件监听器
  204.  
  205. let fullIds = new Set(GM_getValue('fullIds', [])); // 已选满的课程ID
  206.  
  207. // ===== 绘制ui面板中的课程部分 ===================================================================
  208. for (const dept in wishList) {
  209. let courses = wishList[dept];
  210. let deptid = DeptIdMap[dept];
  211. let firstdept = true;
  212. for (const course of courses) {
  213. // 一门课
  214. let name = course.name;
  215. let wishes = course.wishes;
  216. let firstrow = true;
  217. if (wishes.length > 0) {
  218. // wishes里配置了具体的内容
  219. for (let wish of wishes) {
  220. let tr = createElement('tr');
  221. // tab += '<tr>';
  222. if (firstrow) {
  223. if (firstdept) {
  224. let td = createElement('td', { rowspan: wishes.length });
  225. let btn = createElement('button', { id: `bgabtn${bgaBtnId++}`, class: "bgabtn dept jumpdept nowrap", deptid: deptid }, `${dept}🚀`);
  226. td.appendChild(btn);
  227.  
  228. tr.appendChild(td);
  229. firstdept = false;
  230. } else {
  231. let td = createElement('td', { rowspan: wishes.length });
  232. tr.appendChild(td);
  233. }
  234. let btn = createElement('button', { id: `bgabtn${bgaBtnId++}`, class: "bgabtn course copyable jumpcourse", deptid: deptid, name: name }, `${name}🚀`);
  235. let td = createElement('td', { rowspan: wishes.length });
  236. td.appendChild(btn);
  237. tr.appendChild(td);
  238. firstrow = false;
  239. }
  240. // 在ui的每一门课旁边添加选课/学位复选框(如果搜索到的话) =====================================
  241. let courseidspan = getElementsByText($("#regfrm span"), wish.courseid);
  242. if (isCourseSelection && courseidspan.length) {
  243. let row = courseidspan.closest('tr');
  244.  
  245. // 选课
  246. let chk_course_old = row.find('td:first-child input').get(0);
  247. let chk_course = createElement('input', { type: 'checkbox', 'title': '选课', 'style': 'margin-left:2px; margin-right: 2px;' });
  248. chk_course.disabled = chk_course_old.disabled;
  249. // 学位
  250. let chk_deg_old = row.find('td:nth-child(2) input').get(0);
  251. let chk_deg = createElement('input', { type: 'checkbox', 'title': '设为学位课' });
  252. chk_deg.disabled = chk_deg_old.disabled;
  253.  
  254. let td = createElement('td');
  255. let btn = createElement('button', { id: `bgabtn${bgaBtnId++}`, class: "bgabtn courseid copyable nowrap jumpcourseid", deptid: deptid, courseid: wish.courseid }, `${wish.courseid}🚀`);
  256. td.appendChild(chk_course);
  257. td.appendChild(chk_deg);
  258. td.appendChild(btn);
  259.  
  260. tr.appendChild(td);
  261.  
  262. // 记录
  263. chks_course.push({ old: chk_course_old, new: chk_course });
  264. chks_deg.push({ old: chk_deg_old, new: chk_deg });
  265. if (chk_course.disabled) {
  266. fullIds.add(wish.courseid);
  267. }
  268. // =end= 在ui的每一门课旁边添加选课/学位复选框(如果搜索到的话) =============================
  269. } else {
  270. let td = createElement('td');
  271. let btn = createElement('button', { id: `bgabtn${bgaBtnId++}`, class: "bgabtn courseid copyable nowrap jumpcourseid", deptid: deptid, courseid: wish.courseid }, `${wish.courseid}🚀`);
  272. td.appendChild(btn);
  273.  
  274. tr.appendChild(td);
  275. }
  276. let td = createElement('td', { class: 'notes' }, wish.notes);
  277. tr.appendChild(td);
  278. tbody.appendChild(tr);
  279. }
  280. } else {
  281. // wishes为空列表
  282. let tr = createElement('tr');
  283. let td = createElement('td');
  284. let btn = createElement('button', { id: `bgabtn${bgaBtnId++}`, class: "bgabtn dept jumpdept", deptid: deptid }, `${dept}🚀`);
  285. td.appendChild(btn);
  286. tr.appendChild(td);
  287. td = createElement('td');
  288. btn = createElement('button', { id: `bgabtn${bgaBtnId++}`, class: "bgabtn course copyable jumpcourse", deptid: deptid, name: name }, `${name}🚀`);
  289. td.appendChild(btn);
  290. tr.appendChild(td);
  291. tr.appendChild(createElement('td'));
  292. tr.appendChild(createElement('td'));
  293.  
  294. tbody.appendChild(tr)
  295. }
  296. }
  297. }
  298. // =end= 绘制ui面板 ===================================================================
  299.  
  300. // divAppendix 附录栏
  301. // ===== 筛选页面添加按钮 =======================================================
  302. if (isMain) {
  303. // 添加"重置按钮样式"按钮 (暂时没有使用该按钮的需求)
  304. // let bgaResetBtnStyle = createElement(
  305. // 'button',
  306. // { id: 'bgaresetbtnstyle', type: 'submit', class: 'btn btn-primary', title: '重置所有课程编码按钮的样式' },
  307. // '重置按钮样式'
  308. // );
  309. // bgaResetBtnStyle.style.marginLeft = '5px';
  310. // bgaResetBtnStyle.style.marginRight = '5px';
  311. // divAppendix.appendChild(bgaResetBtnStyle);
  312.  
  313. }
  314. // =end= 筛选页面添加按钮 =======================================================
  315.  
  316.  
  317. // ===== 选课页面添加验证码和提交按钮 ===========================================================
  318. else if (isCourseSelection) {
  319.  
  320. // 插入验证码 加在onload事件里保证验证码加载出来
  321. let bgaValiImg = createElement(
  322. 'img',
  323. { id: 'bgaValiImg', title: '点击更换验证码(已修复)', align: 'bottom' }
  324. )
  325. bgaValiImg.style.cursor = 'pointer';
  326. divAppendix.appendChild(bgaValiImg);
  327. // img.width = ValidateImg.width;
  328. // img.height = ValidateImg.height;
  329.  
  330. // 插入验证码输入框
  331. let bgaValiInput = createElement(
  332. 'input',
  333. { id: 'bgavcode', type: 'text' }
  334. );
  335. bgaValiInput.style.width = '50px';
  336. bgaValiInput.style.marginLeft = '5px';
  337. bgaValiInput.style.marginRight = '5px';
  338. divAppendix.appendChild(bgaValiInput);
  339. // 添加"确定提交选课"按钮
  340. let bgaSubmit = createElement(
  341. 'button',
  342. { id: 'bgasubmit', type: 'submit', class: 'btn btn-primary' },
  343. '确定提交选课'
  344. );
  345. divAppendix.appendChild(bgaSubmit);
  346.  
  347. }
  348. // =end= 选课页面添加验证码和提交按钮 ===========================================================
  349.  
  350.  
  351. // ===== 配置各种listener(必须要panel添加到body之后才能设置,在这之前设置的都无效,并且需要重新搜索元素)==========================
  352. if (isMain) {
  353. // 进入筛选学院页面
  354.  
  355. // 一键筛选学院
  356. $(".bgabtn.dept").click(function () {
  357. $(this).addClass('highlight');
  358. let deptid = $(this).attr('deptid');
  359. sumbitFilterDept(deptid);
  360. });
  361.  
  362. // 复制课程代码和课程名称
  363. // $(".copyable").click(function () {
  364. // $(".copyable").removeClass("copied");
  365. // GM_setClipboard($(this).text().replace('🚀', ''));
  366. // $(this).addClass("copied");
  367. // });
  368. // 一键跳转到课程:
  369. // 单击课程名,自动筛选学院后,自动定位到匹配到的第一行,并且匹配项高亮
  370. $('.jumpcourse').click(function () {
  371. $('.jumpcourse').removeClass('highlight');
  372. $('.jumpcourseid').removeClass('highlight');
  373. $(this).addClass('highlight');
  374. let deptid = $(this).attr('deptid');
  375. let coursename = $(this).attr('name');
  376. let btnId = $(this).attr('id'); // 方便跳转后高亮
  377. let behavior = setBehavior('coursename', coursename, null, btnId);
  378. sumbitFilterDept(deptid, behavior);
  379. });
  380.  
  381. // 一键跳转到课程id:
  382. // 单击课程id,自动筛选学院后,自动定位到匹配行,并且匹配项高亮
  383. $('.jumpcourseid').click(function () {
  384. $('.jumpcourse').removeClass('highlight');
  385. $('.jumpcourseid').removeClass('highlight');
  386. $(this).addClass('highlight');
  387. let deptid = $(this).attr('deptid');
  388. let courseid = $(this).attr('courseid');
  389. let btnId = $(this).attr('id'); // 方便跳转后高亮
  390. let behavior = setBehavior('courseid', courseid, null, btnId);
  391. sumbitFilterDept(deptid, behavior);
  392. });
  393.  
  394. // 重置课程编码按钮样式
  395. $('#bgaresetbtnstyle').click(() => {
  396. // remove 'selected' and 'full' for courseid btns
  397. $('.bgabtn.courseid').each((ind, ele) => {
  398. $(ele).prop('disabled', false);
  399. $(ele).removeClass('selected');
  400. $(ele).removeClass('full');
  401. });
  402. // clear storage
  403. GM_setValue('selectedIds', []);
  404. GM_setValue('fullIds', []);
  405. });
  406.  
  407.  
  408. }else if (isCourseSelection) {
  409. // 进入选课页面
  410.  
  411. // 单击课程名,自动定位到匹配到的第一行,并且匹配项高亮
  412. $('.jumpcourse').click(function () {
  413. let coursename = $(this).attr('name');
  414. let btnid = $(this).attr('id');
  415. let behavior = setBehavior('coursename', coursename, null, btnid);
  416. alreadyHighlighted = resolveBehavior(behavior, alreadyHighlighted);
  417. });
  418.  
  419. // 单击课程id,自动定位到所在行,并且匹配项高亮
  420. $('.jumpcourseid').click(function () {
  421. let courseid = $(this).attr('courseid');
  422. let btnid = $(this).attr('id');
  423. let behavior = setBehavior('courseid', courseid, null, btnid);
  424. alreadyHighlighted = resolveBehavior(behavior, alreadyHighlighted);
  425. });
  426.  
  427. // 同步复选框勾选情况
  428. for (const tup of chks_course) {
  429. $(tup.old).change(function() {
  430. $(tup.new).prop("checked", this.checked);
  431. });
  432. $(tup.new).change(function() {
  433. $(tup.old).prop("checked", this.checked);
  434. });
  435. }
  436. for (const tup of chks_deg) {
  437. $(tup.old).change(function() {
  438. $(tup.new).prop("checked", this.checked);
  439. });
  440. $(tup.new).change(function() {
  441. $(tup.old).prop("checked", this.checked);
  442. });
  443. }
  444.  
  445. // 修复原网站中"点击切换验证码"没反应的bug
  446. let valiImg = document.getElementById('adminValidateImg');
  447. valiImg.title = bgaValiImg.title;
  448. valiImg.onclick = function(){
  449. document.getElementById("adminValidateImg").src = '/captchaImage' + "?" + Math.random();
  450. };
  451. // 验证码显示及点击刷新时同步
  452. document.getElementById('bgaValiImg').onclick = function () {
  453. valiImg.onclick();
  454. }
  455. valiImg.addEventListener('load', () => {
  456. document.getElementById('bgaValiImg').src = getBase64Image(valiImg);
  457. });
  458. // 有时刚进去图片就加载了,不会触发onload,需要手动设置src
  459. let dataurl = getBase64Image(valiImg);
  460. if (dataurl != 'data:,') {
  461. document.getElementById('bgaValiImg').src = dataurl;
  462. }
  463. // 同步两个验证码框的输入
  464. $("#bgavcode").on('input', function(){
  465. $("#vcode").val($("#bgavcode").val());
  466. });
  467. $("#vcode").on('input', function(){
  468. $("#bgavcode").val($("#vcode").val());
  469. });
  470. // 提交选课时自动跳过"确认提交吗"对话框
  471. // note: 搜索$("#regfrm").validate,用到了jquery.validate
  472. let validator = $("#regfrm").validate();
  473. // 推测validate包装了form的submit函数,因此去掉这一层包装
  474. $('#regfrm').off("submit");
  475. // 在保留validator的情况下,绕过原本的submit
  476. let subbtn = $('#regfrm button[type="submit"]');
  477. subbtn.prop('id', 'oldsubmit');
  478. subbtn.prop('type', 'button'); // 原submit如果不改的话,点击会触发form的默认submit,就不进行其他验证直接提交了
  479. subbtn.click(function () {
  480. if (validator.form()) {
  481. // 通过验证(勾选选课框+输入验证码)后,触发form原本的submit请求
  482. loading('正在提交,请稍等...');
  483. validator.currentForm.submit()
  484. }
  485. // 否则会直接触发报错提示
  486. });
  487. // ui面板里的提交按钮与原来的按钮同步
  488. $("#bgapanel button[type='submit']").click(function () {
  489. $('#oldsubmit').click();
  490. // $('#regfrm button[type="submit"]').click();
  491. });
  492. }
  493. // =end= 配置各种listener(必须要panel添加到body之后才能设置,在这之前设置的都无效,并且需要重新搜索元素)==========================
  494.  
  495.  
  496. // ===== 在ui中标注已经抢到的课:文字变绿,按钮不可点击 ======================================
  497. // 读取已选择课程列表
  498. let selectedIds = getSelectedIds(isMain);
  499. // 更新已选课程的按钮样式
  500. // selectedIds: Set
  501. // 不论哪个页面都修改ui的状态
  502. for (const selectedId of selectedIds.values()) {
  503. let uiBtn = $(`.bgabtn.courseid[courseid=${selectedId}]`);
  504. if (uiBtn) {
  505. uiBtn.prop('disabled', true);
  506. uiBtn.addClass('selected');
  507. }
  508. }
  509. // =end= 在ui中标注已经抢到的课:文字变绿,按钮不可点击 =================================
  510.  
  511. // ===== 在ui中标注已满的课:文字变红 ======================================
  512. if (isCourseSelection) {
  513. // fullIds数组已在前面添加复选框时更新
  514. // 存到storage里,方便进入学院筛选页面后也可以保持状态
  515. GM_setValue('fullIds', [...fullIds]);
  516. }
  517. // 不论哪个页面都修改ui的状态
  518. for (const fullId of fullIds.values()) {
  519. let uiBtn = $(`.bgabtn.courseid[courseid=${fullId}]`);
  520. if (uiBtn) {
  521. uiBtn.addClass('full');
  522. }
  523. }
  524. // =end= 在ui中标注已满的课:文字变红 ===============================
  525.  
  526. // ===== 可拖动 ===============================
  527. let dragopts = {
  528. setCursor: false,
  529. setPosition: false,
  530. handle: document.getElementById("divHeader"),
  531. onDragEnd: function () {
  532. // 避免出界,设置最大最小值
  533. let frmleft = panel.offsetLeft;
  534. let frmtop = panel.offsetTop;
  535. frmleft = Math.max(0, Math.min(frmleft, innerWidth - 200)); // 0 < frmleft < innerWidth - 100
  536. frmtop = Math.max(0, Math.min(frmtop, innerHeight - 50));
  537.  
  538. panel.style.left = frmleft + "px";
  539. panel.style.top = frmtop + "px";
  540. // 记录left、top
  541. GM_setValue('frmleft', panel.offsetLeft);
  542. GM_setValue('frmtop', panel.offsetTop);
  543. }
  544. };
  545. new Draggable(panel, dragopts);
  546. // =end= 可拖动 ===============================
  547.  
  548. // 一键跳转功能跳转后,插件页面保持之前滚动条的位置
  549. divCourseWish.scrollTop = GM_getValue('scrollTop', 0);
  550. divCourseWish.onscroll = function () {
  551. GM_setValue('scrollTop', divCourseWish.scrollTop);
  552. };
  553.  
  554. // 允许手动调整panel长度,并记录在storage
  555. // 加载panel高度
  556. let frmheight = GM_getValue('frmheight'); // default: undefined
  557. if (frmheight) {
  558. divCourseWish.style.maxHeight = frmheight;
  559. }
  560. // 加载paneltop、left
  561. let frmleft = GM_getValue('frmleft');
  562. if (frmleft) {
  563. panel.style.left = frmleft + 'px';
  564. }
  565. let frmtop = GM_getValue('frmtop');
  566. if (frmtop) {
  567. panel.style.top = frmtop + 'px';
  568. }
  569. addEventListener('resize', Cowboy.debounce(250, function () {
  570. // 调整浏览器大小时,避免ui面板出界,设置最大最小值
  571. let frmleft = panel.offsetLeft;
  572. let frmtop = panel.offsetTop;
  573. frmleft = Math.max(0, Math.min(frmleft, innerWidth - 200)); // 0 < frmleft < innerWidth - 100
  574. frmtop = Math.max(0, Math.min(frmtop, innerHeight - 50));
  575.  
  576. panel.style.left = frmleft + "px";
  577. panel.style.top = frmtop + "px";
  578. // 记录left、top
  579. GM_setValue('frmleft', panel.offsetLeft);
  580. GM_setValue('frmtop', panel.offsetTop);
  581. // TODO 改为面板跟着移动
  582. }));
  583. // 绑定需要拖拽改变大小的元素对象
  584. bindResize(divCourseWish);
  585. function bindResize(el) {
  586. //初始化参数
  587. var els = el.style;
  588. //鼠标的 X 和 Y 轴坐标
  589. var y = 0;
  590. //邪恶的食指
  591. $("#divDrag").mousedown(function (e) {
  592. //按下元素后,计算当前鼠标与对象计算后的坐标
  593. (y = e.clientY - el.offsetHeight);
  594. //在支持 setCapture 做些东东
  595. //绑定事件
  596. $(el).bind("mousemove", mouseMove).bind("mouseup", mouseUp);
  597. $(document.body).bind("mousemove", mouseMove).bind("mouseup", mouseUp);
  598. //防止默认事件发生
  599. e.preventDefault();
  600. });
  601. //移动事件
  602. function mouseMove(e) {
  603. //宇宙超级无敌运算中...
  604. els.maxHeight = e.clientY - y + "px";
  605. }
  606. //停止事件
  607. function mouseUp() {
  608. // 存储高度
  609. GM_setValue('frmheight', divCourseWish.style.maxHeight); // 包含"px"
  610. //卸载事件
  611. $(el)
  612. .unbind("mousemove", mouseMove)
  613. .unbind("mouseup", mouseUp);
  614. $(document.body)
  615. .unbind("mousemove", mouseMove)
  616. .unbind("mouseup", mouseUp);
  617. }
  618. }
  619.  
  620. return panel;
  621. }
  622.  
  623. function getSelectedIds (isMain) {
  624. if (isMain) {
  625. // 在筛选学院页面的话,读取当前已选择课程列表
  626. let selectedIds = new Set(); // 外层已定义
  627. $('table.table tbody tr a[href*=plan]').each((ind, ele) => {
  628. let courseId = ele.text;
  629. selectedIds.add(courseId);
  630. });
  631. // 存到storage里,方便进入选课页面后也可以保持状态
  632. GM_setValue('selectedIds', [...selectedIds]);
  633. return selectedIds;
  634. }
  635. // 否则直接返回记录
  636. return GM_getValue('selectedIds', []);
  637. }
  638.  
  639. function setBehavior(type, data, scrollTop, btnId) {
  640. // 设置跨网页json数据
  641. let behavior = {
  642. 'type': type, // 'courseid' or 'coursename'
  643. 'data': data,
  644. // 'scrollTop': scrollTop, // ui界面滚动条位置。改用storage传输不通过behavior传/
  645. 'btnId': btnId,
  646. }
  647. return behavior;
  648. }
  649.  
  650. function resolveBehavior (behavior, alreadyHighlighted=null) {
  651. // 解析json数据
  652. if (behavior.btnId) {
  653. // 清空其他按钮高亮
  654. $('.jumpcourse').removeClass('highlight');
  655. $('.jumpcourseid').removeClass('highlight');
  656. // 高亮按钮
  657. $(`#${behavior.btnId}`).addClass('highlight');
  658. }
  659. if (behavior.scrollTop) {
  660. // 插件面板滚动条恢复到之前位置
  661. divCourseWish.scrollTop = behavior.scrollTop;
  662. }
  663. let highlighted; // 待高亮DOM
  664. if (behavior.type) {
  665. // 自动滚动定位+高亮课程/课程号
  666. if (behavior.type == 'courseid') {
  667. let courseid = behavior.data;
  668. let courseidspan = getElementsByText($("#regfrm span"), courseid);
  669. // 如果找到
  670. if (courseidspan.length) {
  671. // 跳转到指定位置,并高亮对应行
  672. highlighted = courseidspan;
  673. }
  674. } else if (behavior.type == 'coursename') {
  675. let coursename = behavior.data;
  676. let coursenametag = getElementsByText($("#regfrm a"), coursename, true);
  677. // 如果找到
  678. if (coursenametag.length) {
  679. // 跳转到指定位置,并高亮对应行
  680. highlighted = coursenametag.first(); // 可能有多个匹配,只取第一个
  681. }
  682. }
  683. if (highlighted) {
  684. // 清空其他高亮
  685. if (alreadyHighlighted) {
  686. alreadyHighlighted.css('background-color', '');
  687. } else {
  688. $('#regfrm span[style*=yellow]').css('background-color', '');
  689. $('#regfrm a[style*=yellow]').css('background-color', '');
  690. }
  691. // 高亮匹配项
  692. highlighted.css('background-color', 'yellow');
  693. scrollto(highlighted);
  694. } else {
  695. error('未搜索到课程 ' + behavior.data);
  696. }
  697. }
  698. return highlighted;
  699. }
  700.  
  701. function injectJsonToAction (selector, json) {
  702. let action = $(selector).prop("action");
  703. let jsonstr = JSON.stringify(json);
  704. action = action.replace(/#.+/, '');
  705. action += "#bgabehavior" + jsonstr;
  706. $(selector).prop("action", action);
  707. }
  708.  
  709. function sumbitFilterDept (deptid, behavior) {
  710. // 清空所有勾选情况
  711. $("#regfrm2 input[type='checkbox']").prop('checked', false);
  712. // 勾选当前学院
  713. $(`#${deptid}`).prop("checked", true);
  714. if (behavior) {
  715. injectJsonToAction('#regfrm2', behavior)
  716. }
  717. // 提交
  718. $("#regfrm2 button[type='submit']").submit();
  719. }
  720.  
  721. function getElementsByText(elems, value, isFuzzy=false){
  722. return elems.filter(function (index) {
  723. if (isFuzzy) {
  724. return $(this).text().includes(value);
  725. } else {
  726. return $(this).text() == value;
  727. }
  728. });
  729. }
  730. function randomString(len) {
  731. len = len || 32;
  732. var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
  733. var maxPos = $chars.length;
  734. var pwd = '';
  735. for (i = 0; i < len; i++) {
  736. pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
  737. }
  738. return pwd;
  739. }
  740. function scrollto(jqele) {
  741. // offset header which is 60px in height
  742. let buffer = document.createElement('div');
  743. buffer.id = randomString(5);
  744. buffer.style.display = 'block';
  745. buffer.style.height = '65px';
  746. buffer.style.marginTop = '-65px';
  747. buffer.style.visibility = 'hidden';
  748. $(buffer).insertBefore(jqele);
  749.  
  750. let a = document.createElement('a');
  751. a.href = "#" + buffer.id;
  752. a.click();
  753. }
  754.  
  755. function getBase64Image(img) {
  756. // Create an empty canvas element
  757. var canvas = document.createElement("canvas");
  758. canvas.width = img.naturalWidth;
  759. canvas.height = img.naturalHeight;
  760.  
  761. // Copy the image contents to the canvas
  762. var ctx = canvas.getContext("2d");
  763. ctx.drawImage(img, 0, 0);
  764.  
  765. // Get the data-URL formatted image
  766. // Firefox supports PNG and JPEG. You could check img.src to
  767. // guess the original format, but be aware the using "image/jpg"
  768. // will re-encode the image.
  769. var dataURL = canvas.toDataURL("image/png");
  770. return dataURL;
  771. }
  772.  
  773. function error (msg) {
  774. // 解析时错误处理,错误提示使用选课系统自带的方法
  775. $.jBox.tip(msg);
  776. }
  777.  
  778.  
  779. (function () {
  780. 'use strict';
  781.  
  782. // 登录(不可用)jwxk.ucas.ac.cn后,自动跳转到选课页面
  783. if (window.location.pathname == '/notice/view/1') {
  784. window.location.pathname = '/courseManage/main';
  785. console.log(...prefix('跳转到选课页面'));
  786. }
  787.  
  788. if (window.location.pathname.startsWith('/courseManage/main')) {
  789. // 进入筛选学院页面
  790. let panel = drawPanel('main');
  791.  
  792. }
  793.  
  794. if (window.location.pathname.startsWith('/courseManage/selectCourse')) {
  795. // 进入选课页面
  796. let panel = drawPanel('selectCourse');
  797.  
  798. // 解析跨页json参数(如果有)
  799. let url = window.location.href;
  800. let ind = url.indexOf('#bgabehavior');
  801. if (ind != -1) {
  802. let data = url.substring(ind + '#bgabehavior'.length);
  803. data = decodeURI(data);
  804. let behavior = JSON.parse(data);
  805. alreadyHighlighted = resolveBehavior(behavior, alreadyHighlighted);
  806. }
  807. }
  808. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址