nchu.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // 文件: nchu.js
  2. // 南昌航空大学教务系统课程表导入脚本
  3. function isOnSchedulePage() {
  4. const url = window.location.href;
  5. return /jwc-publish2\.jwc\.nchu\.edu\.cn/i.test(url);
  6. }
  7. // 解析周数字符串,支持逗号/顿号分隔的多段与连续区间
  8. // 例:第4-6,8-19周 / 第5,7,9,11,13,15,17,19周 / 第9周 / 第13-16周
  9. function parseWeeks(timeStr) {
  10. const weeks = new Set();
  11. const segment = timeStr.match(/第\s*([\d、,\-\s]+)\s*周/);
  12. if (!segment) return [];
  13. segment[1].split(/[,、\s]+/).forEach(part => {
  14. part = part.trim();
  15. if (!part) return;
  16. const range = part.match(/^(\d+)\s*-\s*(\d+)$/);
  17. if (range) {
  18. const start = parseInt(range[1], 10);
  19. const end = parseInt(range[2], 10);
  20. for (let i = start; i <= end; i++) weeks.add(i);
  21. } else if (/^\d+$/.test(part)) {
  22. weeks.add(parseInt(part, 10));
  23. }
  24. });
  25. return Array.from(weeks).sort((a, b) => a - b);
  26. }
  27. // 解析节次字符串,兼容 "01~02小节" 与 "01~02节" 两种写法
  28. function parseSections(sectionStr) {
  29. const match = sectionStr.match(/(\d{1,2})~(\d{1,2})(?:小)?节/);
  30. if (match) {
  31. return { start: parseInt(match[1], 10), end: parseInt(match[2], 10) };
  32. }
  33. return null;
  34. }
  35. // 从文档中提取课程数据
  36. function extractCoursesFromDoc(doc) {
  37. const courses = [];
  38. const table = doc.querySelector('.time-table table');
  39. if (!table) {
  40. console.log('未找到课表表格');
  41. return courses;
  42. }
  43. const rows = table.querySelectorAll('tbody tr');
  44. rows.forEach(row => {
  45. const cells = row.querySelectorAll('td');
  46. if (cells.length < 2) return;
  47. // cells[0] 是节次列,cells[1..] 对应周一~周日
  48. for (let i = 1; i < cells.length; i++) {
  49. const day = i;
  50. const cell = cells[i];
  51. // 一个格子里可能有多门课(多个 .item-box)
  52. const itemBoxes = cell.querySelectorAll('.item-box');
  53. itemBoxes.forEach(itemBox => {
  54. parseItemBox(itemBox, day, courses);
  55. });
  56. }
  57. });
  58. return courses;
  59. }
  60. // 解析单个 .item-box(一门课),其内部可能含多个时间段块
  61. function parseItemBox(itemBox, day, courses) {
  62. // 课程全名:.item-box 内第一个 <p>
  63. const courseP = itemBox.querySelector('p');
  64. const name = courseP ? courseP.textContent.trim() : '';
  65. if (!name) return;
  66. // 每个时间段块的构成:<p>课程名</p><div class="tch-name">…</div><div><span item1>教室</span><span item3>周次</span></div>
  67. itemBox.querySelectorAll('.tch-name').forEach(tch => {
  68. const segDiv = tch.nextElementSibling;
  69. if (!segDiv) return;
  70. // 周次:该 div 中 item3.png 所在 span 的文本(如 "第13-16周 星期四")
  71. const weekImg = segDiv.querySelector('img[src*="item3.png"]');
  72. const timeInfo = weekImg && weekImg.parentElement ? weekImg.parentElement.textContent.trim() : '';
  73. const weeks = parseWeeks(timeInfo);
  74. if (weeks.length === 0) return;
  75. // 节次:从 tch-name 内 "01~02节" 的 span 获取
  76. let sectionInfo = null;
  77. tch.querySelectorAll('span').forEach(s => {
  78. if (sectionInfo) return;
  79. sectionInfo = parseSections(s.textContent);
  80. });
  81. if (!sectionInfo) return;
  82. // 教室:该 div 中 item1.png 所在 span 的文字(如 "博学楼F栋-F302")
  83. let room = '';
  84. const roomImg = segDiv.querySelector('img[src*="item1.png"]');
  85. if (roomImg && roomImg.parentElement) {
  86. room = roomImg.parentElement.textContent.trim();
  87. }
  88. // 教师:在本块 tch-name 的 span 中找 "教师:xxx"(避免与学分/节次拼接)
  89. let teacher = '';
  90. tch.querySelectorAll('span').forEach(s => {
  91. if (teacher) return;
  92. const m = s.textContent.match(/教师[::]\s*(.+)/);
  93. if (m) teacher = m[1].trim();
  94. });
  95. courses.push({
  96. name,
  97. teacher,
  98. position: room || '未指定',
  99. day,
  100. startSection: sectionInfo.start,
  101. endSection: sectionInfo.end,
  102. weeks
  103. });
  104. });
  105. }
  106. // 获取当前页面的课程(兼容课表页直接注入,或从首页 iframe 中取课表内容)
  107. function getCurrentWeekCourses() {
  108. // 若当前文档自身就是课表页,直接解析
  109. if (document.querySelector('.time-table table')) {
  110. return extractCoursesFromDoc(document);
  111. }
  112. // 否则遍历 iframe 查找课表页
  113. const iframes = Array.from(document.querySelectorAll('iframe'));
  114. for (const iframe of iframes) {
  115. try {
  116. if (iframe.contentDocument && iframe.contentDocument.querySelector('.time-table table')) {
  117. return extractCoursesFromDoc(iframe.contentDocument);
  118. }
  119. } catch (e) {
  120. // 跨域 iframe 跳过
  121. }
  122. }
  123. return [];
  124. }
  125. // 去重合并课程
  126. function mergeAndDeduplicateCourses(allCourses) {
  127. const courseMap = new Map();
  128. allCourses.forEach(course => {
  129. const key = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.teacher}-${course.position}`;
  130. if (!courseMap.has(key)) {
  131. courseMap.set(key, {
  132. ...course,
  133. weeks: [...course.weeks]
  134. });
  135. } else {
  136. const existing = courseMap.get(key);
  137. const weekSet = new Set([...existing.weeks, ...course.weeks]);
  138. existing.weeks = Array.from(weekSet).sort((a, b) => a - b);
  139. }
  140. });
  141. return Array.from(courseMap.values());
  142. }
  143. // 生成时间段配置(该校实际为 11 节课)
  144. function generateTimeSlots() {
  145. return [
  146. { "number": 1, "startTime": "08:00", "endTime": "08:45" },
  147. { "number": 2, "startTime": "08:55", "endTime": "09:40" },
  148. { "number": 3, "startTime": "10:00", "endTime": "10:45" },
  149. { "number": 4, "startTime": "10:55", "endTime": "11:40" },
  150. { "number": 5, "startTime": "14:00", "endTime": "14:45" },
  151. { "number": 6, "startTime": "14:55", "endTime": "15:40" },
  152. { "number": 7, "startTime": "16:00", "endTime": "16:45" },
  153. { "number": 8, "startTime": "16:55", "endTime": "17:40" },
  154. { "number": 9, "startTime": "19:00", "endTime": "19:45" },
  155. { "number": 10, "startTime": "19:55", "endTime": "20:40" },
  156. { "number": 11, "startTime": "20:50", "endTime": "21:35" }
  157. ];
  158. }
  159. // 主函数:导入课程
  160. async function importCourseSchedule() {
  161. try {
  162. console.log('开始导入课程表...');
  163. window.shiguangBridge.showToast('正在获取课表数据...');
  164. // 获取当前周课程
  165. const courses = getCurrentWeekCourses();
  166. console.log(`找到 ${courses.length} 门课程`);
  167. if (courses.length === 0) {
  168. window.shiguangBridge.showToast('未找到课程数据');
  169. return false;
  170. }
  171. console.log('课程数据:', courses);
  172. // 导入课程
  173. const coursesResult = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  174. if (coursesResult === true) {
  175. console.log('课程导入成功!');
  176. window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
  177. } else {
  178. console.log('课程导入失败');
  179. window.shiguangBridge.showToast('课程导入失败');
  180. return false;
  181. }
  182. // 生成并导入时间段
  183. const finalTimeSlots = generateTimeSlots();
  184. const timeSlotsResult = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(finalTimeSlots));
  185. if (timeSlotsResult === true) {
  186. console.log('时间段导入成功!');
  187. }
  188. return true;
  189. } catch (error) {
  190. console.error('导入过程出错:', error);
  191. window.shiguangBridge.showToast('导入失败: ' + error.message);
  192. return false;
  193. }
  194. }
  195. // ========== 主执行逻辑 ==========
  196. if (isOnSchedulePage()) {
  197. console.log('检测到南昌航空大学教务系统');
  198. window.shiguangBridge.showToast('正在准备导入课程表...');
  199. setTimeout(async () => {
  200. const success = await importCourseSchedule();
  201. if (success) {
  202. window.shiguangBridge.notifyTaskCompletion();
  203. }
  204. }, 2000);
  205. } else {
  206. console.log('当前不在教务系统页面');
  207. window.shiguangBridge.showToast('请先登录教务系统!');
  208. }