imnc_01.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /**
  2. * 呼和浩特民族学院 (IMNC) 课表解析脚本-通过 WebVPN 登录
  3. * 放置于测试目录用于真机测试
  4. */
  5. // 清理 <wbr> 标签
  6. function cleanWbr(str) {
  7. return str ? str.replace(/<wbr\s*\/?>/gi, '') : str;
  8. }
  9. // 周次解析函数
  10. function parseWeeks(weekStr) {
  11. let weeks = [];
  12. if (!weekStr) return weeks;
  13. let isSingle = weekStr.includes('单');
  14. let isDouble = weekStr.includes('双');
  15. // 匹配 "1-16", "第1-9周"
  16. let match = weekStr.match(/(\d+)-(\d+)/);
  17. if (match) {
  18. let start = parseInt(match[1]);
  19. let end = parseInt(match[2]);
  20. for (let i = start; i <= end; i++) {
  21. if (isSingle && i % 2 === 0) continue;
  22. if (isDouble && i % 2 !== 0) continue;
  23. weeks.push(i);
  24. }
  25. } else {
  26. // 匹配 "第13周"
  27. let singleMatch = weekStr.match(/(\d+)/);
  28. if (singleMatch) {
  29. weeks.push(parseInt(singleMatch[1]));
  30. }
  31. }
  32. return weeks;
  33. }
  34. // 核心解析函数
  35. function fetchAndParseCourses() {
  36. let courses = [];
  37. let table = document.querySelector('#timetable');
  38. if (!table) return null;
  39. let rows = table.querySelectorAll('tr');
  40. // 第 0 行是表头,从第 1 行开始遍历节次
  41. for (let i = 1; i < rows.length; i++) {
  42. let row = rows[i];
  43. let cells = row.querySelectorAll('td');
  44. let section = i; // 1 到 13 节
  45. for (let j = 0; j < cells.length; j++) {
  46. let cell = cells[j];
  47. let day = j + 1; // 星期 1 到 7
  48. let html = cell.innerHTML.trim();
  49. if (!html || html === '&nbsp;') continue;
  50. // 按 <br> 分割
  51. let parts = html.split(/<br\s*\/?>/i).map(s => s.trim()).filter(s => s !== '');
  52. // 每 5 个元素代表一个完整的课程块
  53. for (let k = 0; k < parts.length; k += 5) {
  54. if (k + 3 >= parts.length) break;
  55. let namePart = parts[k];
  56. let position = parts[k+1];
  57. let teacher = parts[k+2];
  58. let weekStr = parts[k+3];
  59. // parts[k+4] 是 "讲授" 等类型,暂时不需要存入
  60. // 提取书名号内的课程名
  61. let nameMatch = namePart.match(/&lt;&lt;(.*?)&gt;&gt;/);
  62. let name = nameMatch ? nameMatch[1] : namePart;
  63. // 兼容某些浏览器可能将转义符还原的情况
  64. if (!nameMatch) {
  65. let nameMatch2 = namePart.match(/<<(.*?)>>/);
  66. if (nameMatch2) name = nameMatch2[1];
  67. }
  68. // 清理 <wbr> 标签
  69. name = cleanWbr(name);
  70. position = cleanWbr(position);
  71. teacher = cleanWbr(teacher);
  72. let weeks = parseWeeks(weekStr);
  73. // 查找同一天、同名、同老师、同地点、同周次,且正好是上一节的课程(合并连上的课)
  74. let existingCourse = courses.find(c =>
  75. c.name === name &&
  76. c.day === day &&
  77. c.teacher === teacher &&
  78. c.position === position &&
  79. JSON.stringify(c.weeks) === JSON.stringify(weeks) &&
  80. c.endSection === section - 1
  81. );
  82. if (existingCourse) {
  83. existingCourse.endSection = section;
  84. } else {
  85. courses.push({
  86. name: name,
  87. teacher: teacher,
  88. position: position,
  89. day: day,
  90. startSection: section,
  91. endSection: section,
  92. weeks: weeks
  93. });
  94. }
  95. }
  96. }
  97. }
  98. return courses;
  99. }
  100. function cleanCellText(cell) {
  101. return cell.textContent.replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim();
  102. }
  103. function parseNoArrangementCourses() {
  104. let table = document.querySelector('#noArrangement');
  105. if (!table) return [];
  106. let rows = table.querySelectorAll('tr');
  107. let courses = [];
  108. let seen = {};
  109. for (let i = 1; i < rows.length; i++) {
  110. let cells = rows[i].querySelectorAll('td');
  111. if (cells.length < 8) continue;
  112. let course = {
  113. courseId: cleanCellText(cells[0]),
  114. name: cleanCellText(cells[1]),
  115. sequence: cleanCellText(cells[2]),
  116. teacher: cleanCellText(cells[3]) || '未填写教师',
  117. combinedClass: cleanCellText(cells[4]),
  118. weeks: cleanCellText(cells[5]),
  119. day: cleanCellText(cells[6]),
  120. position: cleanCellText(cells[7])
  121. };
  122. if (!course.courseId && !course.name) continue;
  123. let key = [course.courseId, course.name, course.sequence, course.teacher, course.combinedClass, course.weeks, course.day, course.position].join('|');
  124. if (seen[key]) continue;
  125. seen[key] = true;
  126. courses.push(course);
  127. }
  128. return courses;
  129. }
  130. function formatNoArrangementMessage(courses) {
  131. let lines = courses.map((course, index) => {
  132. let teacher = course.teacher || '未填写教师';
  133. let weeks = course.weeks || '未填写周次';
  134. return `${index + 1}. ${course.name} / ${teacher} / ${weeks}`;
  135. });
  136. return `检测到 ${courses.length} 门课程没有具体上课时间或地点,无法自动放入课表:\n\n${lines.join('\n')}\n\n请在确认课程时间后重新导入,点击【继续】将导入已知课程。`;
  137. }
  138. // 调度流程
  139. async function runImportFlow() {
  140. try {
  141. AndroidBridge.showToast("开始解析课表...");
  142. const alertConfirmed = await window.AndroidBridgePromise.showAlert(
  143. "导入确认",
  144. "请确保您目前处于教务系统的“学生课表”显示页面。\n是否立即提取并导入课表?",
  145. "开始提取"
  146. );
  147. if (!alertConfirmed) {
  148. AndroidBridge.showToast("导入已取消");
  149. return;
  150. }
  151. let courses = fetchAndParseCourses();
  152. if (!courses || courses.length === 0) {
  153. await window.AndroidBridgePromise.showAlert("错误", "未在当前页面找到课表数据,请确认是否处于课表页面,或联系适配开发者。", "好的");
  154. return;
  155. }
  156. let noArrangementCourses = parseNoArrangementCourses();
  157. if (noArrangementCourses.length > 0) {
  158. await window.AndroidBridgePromise.showAlert(
  159. "存在未安排课程",
  160. formatNoArrangementMessage(noArrangementCourses),
  161. "继续"
  162. );
  163. }
  164. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  165. AndroidBridge.showToast(`成功导入 ${courses.length} 门课程块!`);
  166. AndroidBridge.notifyTaskCompletion();
  167. } catch (error) {
  168. AndroidBridge.showToast("导入发生错误: " + error.message);
  169. }
  170. }
  171. // 启动执行
  172. runImportFlow();