cczu.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /**
  2. * 常州大学 (CCZU) 课表适配器
  3. * 系统: .NET ASPX 自研教务
  4. * 课表页面: /web_jxrw/cx_kb_xsgrkb.aspx (iframe)
  5. * 数据位置: HTML 表格 #GVxkkb
  6. */
  7. (function () {
  8. "use strict";
  9. function parseWeeks(raw) {
  10. if (!raw || raw === "/") return [];
  11. const cleaned = raw.replace(/\/+$/, "").trim();
  12. if (!cleaned) return [];
  13. const weeks = [];
  14. const parts = cleaned.split(",");
  15. for (const part of parts) {
  16. const trimmed = part.trim();
  17. if (!trimmed) continue;
  18. const rangeMatch = trimmed.match(/^(\d+)\s*[-~]\s*(\d+)$/);
  19. if (rangeMatch) {
  20. const start = parseInt(rangeMatch[1], 10);
  21. const end = parseInt(rangeMatch[2], 10);
  22. for (let w = start; w <= end; w++) weeks.push(w);
  23. } else {
  24. const num = parseInt(trimmed, 10);
  25. if (!isNaN(num)) weeks.push(num);
  26. }
  27. }
  28. return [...new Set(weeks)];
  29. }
  30. function parseSection(raw) {
  31. if (!raw) return null;
  32. const n = parseInt(raw.trim(), 10);
  33. return isNaN(n) ? null : n;
  34. }
  35. function parseCourseCell(cellText) {
  36. if (!cellText || cellText === "\u00a0" || cellText.trim() === "") return [];
  37. const courses = [];
  38. const entries = cellText.split("/");
  39. for (const entry of entries) {
  40. const trimmed = entry.trim();
  41. if (!trimmed) continue;
  42. const match = trimmed.match(
  43. /^(.+?)\s+([A-Za-z0-9\u4e00-\u9fff]+(?:机房|阶)?)\s+([\d,\-~]+(?:,\d+[\-~]\d+)*)\s*,?\s*(.*)$/
  44. );
  45. if (match) {
  46. const courseName = match[1].trim();
  47. const room = match[2].trim();
  48. const weeksRaw = match[3].trim();
  49. const teacher = match[4].trim().replace(/\/+$/, "").trim() || "";
  50. courses.push({
  51. courseName,
  52. room,
  53. teacher,
  54. weeksRaw,
  55. weeks: parseWeeks(weeksRaw),
  56. });
  57. } else {
  58. const simpleMatch = trimmed.match(/^(.+?)\s+([A-Za-z0-9\u4e00-\u9fff]+(?:机房|阶)?)\s*/);
  59. if (simpleMatch) {
  60. courses.push({
  61. courseName: simpleMatch[1].trim(),
  62. room: simpleMatch[2].trim(),
  63. teacher: "",
  64. weeksRaw: "",
  65. weeks: [],
  66. });
  67. }
  68. }
  69. }
  70. return courses;
  71. }
  72. function extractTeacherMap(doc) {
  73. const map = {};
  74. const target = doc || document;
  75. const table = target.getElementById("GVxkall");
  76. if (!table) return map;
  77. const rows = table.querySelectorAll("tr.dg1-item");
  78. for (const row of rows) {
  79. const cells = row.querySelectorAll("td");
  80. if (cells.length >= 6) {
  81. const name = cells[1]?.textContent?.trim();
  82. const teacher = cells[5]?.textContent?.trim();
  83. if (name && teacher && teacher !== "\u00a0") {
  84. map[name] = teacher;
  85. }
  86. }
  87. }
  88. return map;
  89. }
  90. function parseSemester(raw) {
  91. if (!raw) return null;
  92. const m = raw.trim().match(/^(\d{2})-(\d{2})-(\d)$/);
  93. if (!m) return null;
  94. const startYear = 2000 + parseInt(m[1], 10);
  95. const endYear = 2000 + parseInt(m[2], 10);
  96. const semester = parseInt(m[3], 10);
  97. return { startYear, endYear, semester };
  98. }
  99. function findTable() {
  100. let table = document.getElementById("GVxkkb");
  101. if (table) return { doc: document, table };
  102. const iframes = document.querySelectorAll("iframe");
  103. for (const iframe of iframes) {
  104. try {
  105. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  106. if (!iframeDoc) continue;
  107. table = iframeDoc.getElementById("GVxkkb");
  108. if (table) return { doc: iframeDoc, table };
  109. const innerIframes = iframeDoc.querySelectorAll("iframe");
  110. for (const inner of innerIframes) {
  111. try {
  112. const innerDoc = inner.contentDocument || inner.contentWindow.document;
  113. if (!innerDoc) continue;
  114. table = innerDoc.getElementById("GVxkkb");
  115. if (table) return { doc: innerDoc, table };
  116. } catch (e) {}
  117. }
  118. } catch (e) {}
  119. }
  120. return null;
  121. }
  122. const TimeSlots = [
  123. { number: 1, startTime: "08:00", endTime: "08:40" },
  124. { number: 2, startTime: "08:45", endTime: "09:25" },
  125. { number: 3, startTime: "09:45", endTime: "10:25" },
  126. { number: 4, startTime: "10:35", endTime: "11:15" },
  127. { number: 5, startTime: "11:20", endTime: "12:00" },
  128. { number: 6, startTime: "13:30", endTime: "14:10" },
  129. { number: 7, startTime: "14:15", endTime: "14:55" },
  130. { number: 8, startTime: "15:15", endTime: "15:55" },
  131. { number: 9, startTime: "16:00", endTime: "16:40" },
  132. { number: 10, startTime: "18:30", endTime: "19:10" },
  133. { number: 11, startTime: "19:15", endTime: "19:55" },
  134. { number: 12, startTime: "20:05", endTime: "20:45" }
  135. ];
  136. async function importPresetTimeSlots(timeSlots) {
  137. if (timeSlots.length === 0) return;
  138. try { await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots)); } catch (e) {}
  139. }
  140. async function runImportFlow() {
  141. const result = findTable();
  142. if (!result) {
  143. window.shiguangBridge.showToast("找不到课表表格,请确保已进入课表页面");
  144. return;
  145. }
  146. const { doc, table } = result;
  147. const teacherMap = extractTeacherMap(doc);
  148. const courses = [];
  149. const rows = table.querySelectorAll("tr");
  150. const semesterSelect = doc.getElementById("DDxq");
  151. const semesterRaw = semesterSelect?.value || "";
  152. const semester = parseSemester(semesterRaw);
  153. for (let i = 1; i < rows.length; i++) {
  154. const cells = rows[i].querySelectorAll("td");
  155. if (cells.length < 8) continue;
  156. const section = parseSection(cells[0]?.textContent);
  157. if (section === null) continue;
  158. for (let day = 1; day <= 7; day++) {
  159. const cellText = cells[day]?.textContent?.trim() || "";
  160. if (!cellText || cellText === "\u00a0") continue;
  161. const parsed = parseCourseCell(cellText);
  162. for (const c of parsed) {
  163. const mergedTeacher = c.teacher || teacherMap[c.courseName] || "";
  164. courses.push({
  165. name: c.courseName,
  166. teacher: mergedTeacher,
  167. position: c.room,
  168. day: day,
  169. startSection: section,
  170. endSection: section + 1,
  171. weeks: c.weeks,
  172. });
  173. }
  174. }
  175. }
  176. if (courses.length === 0) {
  177. window.shiguangBridge.showToast("未找到课程数据");
  178. return;
  179. }
  180. try {
  181. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  182. await importPresetTimeSlots(TimeSlots);
  183. window.shiguangBridge.showToast(`课程导入成功,共 ${courses.length} 门课程!`);
  184. window.shiguangBridge.notifyTaskCompletion();
  185. } catch (error) {
  186. window.shiguangBridge.showToast("课程保存失败: " + error.message);
  187. }
  188. }
  189. if (document.readyState === "loading") {
  190. document.addEventListener("DOMContentLoaded", runImportFlow);
  191. } else {
  192. runImportFlow();
  193. }
  194. })();