tsu_01.js 7.3 KB

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