urp_01.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. // 通用 URP 教务拾光课程表适配脚本
  2. /**
  3. * 解析复杂的周次文本
  4. * 示例: "1-8,10-17周" -> [1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17]
  5. * 示例: "1-5,7-8,10-12周双周,13-17周" -> 自动过滤单双周
  6. */
  7. function parseWeekText(weekStr) {
  8. let weeks = new Set();
  9. if (!weekStr) return [];
  10. // 清理无用后缀,按逗号或分号切割
  11. const rawSegments = weekStr.replace(/周/g, '').split(/[,,;;]/);
  12. rawSegments.forEach(segment => {
  13. let isEven = segment.includes('双');
  14. let isOdd = segment.includes('单');
  15. let cleanSegment = segment.replace(/(单|双)/g, '').replace(/第/g, '').trim();
  16. if (cleanSegment.includes('-')) {
  17. const [start, end] = cleanSegment.split('-').map(Number);
  18. if (!isNaN(start) && !isNaN(end)) {
  19. for (let i = start; i <= end; i++) {
  20. if (isEven && i % 2 !== 0) continue;
  21. if (isOdd && i % 2 === 0) continue;
  22. weeks.add(i);
  23. }
  24. }
  25. } else {
  26. const num = Number(cleanSegment);
  27. if (!isNaN(num) && num > 0) {
  28. weeks.add(num);
  29. }
  30. }
  31. });
  32. return Array.from(weeks).sort((a, b) => a - b);
  33. }
  34. /**
  35. * 解析节次文本
  36. */
  37. function parseSectionText(sectionStr) {
  38. let startSection = 1;
  39. let endSection = 1;
  40. if (!sectionStr) return { startSection, endSection };
  41. const match = sectionStr.match(/(\d+)-(\d+)节?/);
  42. if (match) {
  43. startSection = parseInt(match[1]);
  44. endSection = parseInt(match[2]);
  45. } else {
  46. const singleMatch = sectionStr.match(/(\d+)节?/);
  47. if (singleMatch) {
  48. startSection = parseInt(singleMatch[1]);
  49. endSection = parseInt(singleMatch[1]);
  50. }
  51. }
  52. return { startSection, endSection };
  53. }
  54. /**
  55. * 从表格中动态解析时间段信息 (寻找 id="0_x" 的 th)
  56. */
  57. function parseTimeSlots() {
  58. let timeSlots = [];
  59. const timeThs = document.querySelectorAll('th[id^="0_"]');
  60. timeThs.forEach(th => {
  61. const idParts = th.id.split('_');
  62. const sectionNumber = parseInt(idParts[1]); // 获取节次序号
  63. const text = th.textContent || "";
  64. // 匹配格式如 "(08:00-08:45)"
  65. const timeMatch = text.match(/\((\d{2}:\d{2})-(\d{2}:\d{2})\)/);
  66. if (timeMatch && !isNaN(sectionNumber)) {
  67. timeSlots.push({
  68. number: sectionNumber,
  69. startTime: timeMatch[1],
  70. endTime: timeMatch[2]
  71. });
  72. }
  73. });
  74. return timeSlots.sort((a, b) => a.number - b.number);
  75. }
  76. /**
  77. * 核心解析:基于 HTML DOM 结构解析课程数据
  78. */
  79. async function fetchAndParseJwData() {
  80. try {
  81. AndroidBridge.showToast("正在解析网页课表...");
  82. let courses = [];
  83. // 获取所有带有有效 id (格式如 2_5) 且内部包含课程块的单元格
  84. const allTds = document.querySelectorAll('td[id*="_"]');
  85. allTds.forEach(td => {
  86. const idParts = td.id.split('_');
  87. if (idParts.length !== 2) return;
  88. // _前面从1到7代表星期
  89. const day = parseInt(idParts[0]);
  90. if (isNaN(day) || day < 1 || day > 7) return;
  91. // 找到单元格内所有的课程卡片
  92. const classDivs = td.querySelectorAll('.class_div');
  93. classDivs.forEach(div => {
  94. const pTags = div.querySelectorAll('p');
  95. if (pTags.length < 5) return; // 格式不健全的格子直接跳过
  96. const name = pTags[0].textContent.trim();
  97. const teacher = pTags[2].textContent.replace(/^[\s*]+|[\s*]+$/g, '').replace(/\*/g, ' ').replace(/\s+/g, ' ');
  98. const weekStr = pTags[3].textContent.trim();
  99. const sectionStr = pTags[4].textContent.trim();
  100. const position = pTags[5] ? pTags[5].textContent.trim() : "未知地点";
  101. // 解析周次与真实的开始/结束节次
  102. const weeks = parseWeekText(weekStr);
  103. const { startSection, endSection } = parseSectionText(sectionStr);
  104. if (name && weeks.length > 0) {
  105. courses.push({
  106. name: name,
  107. teacher: teacher,
  108. position: position,
  109. day: day,
  110. startSection: startSection,
  111. endSection: endSection,
  112. weeks: weeks
  113. });
  114. }
  115. });
  116. });
  117. // 动态提取时间段
  118. const timeSlots = parseTimeSlots();
  119. if (courses.length === 0) {
  120. throw new Error("未能在当前页面检测到有效的课表数据,请确认是否处于课表视图页面");
  121. }
  122. return { courses, timeSlots };
  123. } catch (e) {
  124. console.error("HTML解析失败详情:", e);
  125. AndroidBridge.showToast("同步失败: " + e.message);
  126. return null;
  127. }
  128. }
  129. /**
  130. * 辅助:保存数据到外部 APP
  131. */
  132. async function saveToApp(result) {
  133. const courseSuccess = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(result.courses));
  134. if (!courseSuccess) return false;
  135. if (result.timeSlots && result.timeSlots.length > 0) {
  136. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(result.timeSlots));
  137. }
  138. return true;
  139. }
  140. /**
  141. * 流程控制流程
  142. */
  143. async function runImportFlow() {
  144. const alertResult = await window.AndroidBridgePromise.showAlert(
  145. "教务网页课表导入",
  146. "请确保您当前的网页已加载出课表视图后再开始导入",
  147. "开始同步"
  148. );
  149. if (!alertResult) return;
  150. const result = await fetchAndParseJwData();
  151. if (!result || result.courses.length === 0) return;
  152. if (await saveToApp(result)) {
  153. AndroidBridge.showToast(`成功从网页导入 ${result.courses.length} 个课程时段`);
  154. AndroidBridge.notifyTaskCompletion();
  155. }
  156. }
  157. // 启动导入流程
  158. runImportFlow();