tsu_01.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. function parseWeeks(weekStr) {
  2. const weeks = [];
  3. weekStr.split(',').forEach(part => {
  4. if (part.includes('-')) {
  5. const [start, end] = part.split('-').map(Number);
  6. for (let i = start; i <= end; i++) weeks.push(i);
  7. } else {
  8. const w = parseInt(part);
  9. if (!isNaN(w)) weeks.push(w);
  10. }
  11. });
  12. return weeks;
  13. }
  14. function findTable(win) {
  15. const t = Array.from(win.document.querySelectorAll('table'))
  16. .find(x => x.innerText.includes("星期一") && x.innerText.includes("["));
  17. if (t) return t;
  18. for (let i = 0; i < win.frames.length; i++) {
  19. try {
  20. const st = findTable(win.frames[i]);
  21. if (st) return st;
  22. } catch (e) {}
  23. }
  24. return null;
  25. }
  26. async function fetchAndParseCourses() {
  27. const table = findTable(window);
  28. if (!table) {
  29. throw new Error("未检测到课表数据,请确保已切换到显示课表的页面!");
  30. }
  31. const rawItems = [];
  32. Array.from(table.rows).forEach(row => {
  33. const cells = Array.from(row.cells);
  34. if (cells.length < 7) return;
  35. cells.forEach((cell, colIndex) => {
  36. const distanceToLast = cells.length - 1 - colIndex;
  37. if (distanceToLast > 6) return;
  38. const day = 7 - distanceToLast;
  39. const rawText = cell.innerText.trim();
  40. if (!rawText.includes('[')) return;
  41. // 过滤空行、清理隐藏的乱码字符
  42. const lines = rawText.split('\n').map(l => l.replace(/[\s\u200B-\u200D\uFEFF]/g, '').trim()).filter(l => l);
  43. // 找出所有时间的行号(锚点)
  44. const timeIndices = [];
  45. lines.forEach((line, index) => {
  46. if (/([\d\-,]+)\[(\d+)-(\d+)\]/.test(line)) {
  47. timeIndices.push(index);
  48. }
  49. });
  50. // 遍历每个锚点,精准抓取
  51. timeIndices.forEach((currTimeIdx, k) => {
  52. const nextTimeIdx = (k + 1 < timeIndices.length) ? timeIndices[k + 1] : lines.length;
  53. const match = lines[currTimeIdx].match(/([\d\-,]+)\[(\d+)-(\d+)\]/);
  54. if (match) {
  55. // 1. 抓取课名和老师
  56. let name = "未知课程";
  57. let teacher = "未知教师";
  58. let nameTeacherLines = (k === 0) ? lines.slice(0, currTimeIdx) : lines.slice(timeIndices[k - 1] + 1, currTimeIdx);
  59. if (nameTeacherLines.length > 2) {
  60. nameTeacherLines = nameTeacherLines.slice(nameTeacherLines.length - 2);
  61. }
  62. if (nameTeacherLines.length >= 2) {
  63. name = nameTeacherLines[0];
  64. teacher = nameTeacherLines[1];
  65. } else if (nameTeacherLines.length === 1) {
  66. name = nameTeacherLines[0];
  67. teacher = ""; // 没写老师
  68. }
  69. // 2. 抓取地点
  70. let position = "";
  71. const gap = nextTimeIdx - currTimeIdx - 1; // 距离下个时间(或结尾)差几行
  72. if (k === timeIndices.length - 1) {
  73. // 最后一门课,后面全是地点
  74. if (gap > 0) position = lines.slice(currTimeIdx + 1).join(' ');
  75. } else {
  76. if (gap === 3) {
  77. position = lines[currTimeIdx + 1]; // 刚好3行,第1行必是地点
  78. } else if (gap > 3) {
  79. position = lines.slice(currTimeIdx + 1, nextTimeIdx - 2).join(' '); // 地点占了多行
  80. } else {
  81. position = ""; // <= 2行说明全被下节课的名字占了,这节课没地点(网课)
  82. }
  83. }
  84. rawItems.push({
  85. name: name,
  86. teacher: teacher,
  87. position: position,
  88. day: day,
  89. startSection: parseInt(match[2]),
  90. endSection: parseInt(match[3]),
  91. weeks: parseWeeks(match[1])
  92. });
  93. }
  94. });
  95. });
  96. });
  97. const groupMap = new Map();
  98. rawItems.forEach(item => {
  99. const key = `${item.name}|${item.teacher}|${item.position}|${item.day}`;
  100. if (!groupMap.has(key)) groupMap.set(key, {});
  101. const weekMap = groupMap.get(key);
  102. item.weeks.forEach(w => {
  103. if (!weekMap[w]) weekMap[w] = new Set();
  104. for (let s = item.startSection; s <= item.endSection; s++) {
  105. weekMap[w].add(s);
  106. }
  107. });
  108. });
  109. const finalCourses = [];
  110. groupMap.forEach((weekMap, key) => {
  111. const [name, teacher, position, day] = key.split('|');
  112. const patternMap = new Map();
  113. Object.keys(weekMap).forEach(w => {
  114. const week = parseInt(w);
  115. const sections = Array.from(weekMap[week]).sort((a, b) => a - b);
  116. if (sections.length === 0) return;
  117. let start = sections[0];
  118. for (let i = 0; i < sections.length; i++) {
  119. if (i === sections.length - 1 || sections[i+1] !== sections[i] + 1) {
  120. const pKey = `${start}-${sections[i]}`;
  121. if (!patternMap.has(pKey)) patternMap.set(pKey, []);
  122. patternMap.get(pKey).push(week);
  123. if (i < sections.length - 1) start = sections[i+1];
  124. }
  125. }
  126. });
  127. patternMap.forEach((weeks, pKey) => {
  128. const [sStart, sEnd] = pKey.split('-').map(Number);
  129. finalCourses.push({
  130. name, teacher, position,
  131. day: parseInt(day),
  132. startSection: sStart,
  133. endSection: sEnd,
  134. weeks: weeks.sort((a, b) => a - b)
  135. });
  136. });
  137. });
  138. return finalCourses;
  139. }
  140. async function runImportFlow() {
  141. try {
  142. AndroidBridge.showToast("泰山学院引擎启动,抓取数据中...");
  143. const courses = await fetchAndParseCourses();
  144. if (!courses || courses.length === 0) {
  145. AndroidBridge.showToast("解析完成,但当前课表为空");
  146. AndroidBridge.notifyTaskCompletion();
  147. return;
  148. }
  149. const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  150. if (saveResult === true) {
  151. AndroidBridge.showToast(`导入大成功!合并生成 ${courses.length} 个课块`);
  152. AndroidBridge.notifyTaskCompletion();
  153. }
  154. } catch (error) {
  155. AndroidBridge.showToast("⚠️ " + error.message);
  156. AndroidBridge.notifyTaskCompletion();
  157. }
  158. }
  159. runImportFlow();