hit.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // 哈尔滨工业大学(hit.edu.cn) 研究生课表适配脚本
  2. // 研究生教育管理系统
  3. function parseWeeks(weekStr) {
  4. if (!weekStr) return [];
  5. const cleaned = weekStr.replace(/周/g, '').trim();
  6. if (!cleaned) return [];
  7. const weeks = [];
  8. const parts = cleaned.split(/[,,]/);
  9. for (const part of parts) {
  10. const trimmed = part.trim();
  11. if (!trimmed) continue;
  12. const isOdd = trimmed.startsWith('单');
  13. const isEven = trimmed.startsWith('双');
  14. const rangeStr = isOdd || isEven ? trimmed.substring(1) : trimmed;
  15. const rangeMatch = rangeStr.match(/^(\d+)\s*[-~]\s*(\d+)$/);
  16. const singleMatch = rangeStr.match(/^(\d+)$/);
  17. if (rangeMatch) {
  18. const start = parseInt(rangeMatch[1], 10);
  19. const end = parseInt(rangeMatch[2], 10);
  20. for (let w = start; w <= end; w++) {
  21. if (isOdd && w % 2 === 0) continue;
  22. if (isEven && w % 2 !== 0) continue;
  23. weeks.push(w);
  24. }
  25. } else if (singleMatch) {
  26. const num = parseInt(singleMatch[1], 10);
  27. if (isOdd && num % 2 === 0) continue;
  28. if (isEven && num % 2 !== 0) continue;
  29. weeks.push(num);
  30. }
  31. }
  32. return [...new Set(weeks)].sort((a, b) => a - b);
  33. }
  34. function parseCourseCell(cellStr, day, sections) {
  35. if (!cellStr || cellStr === 'null') return [];
  36. const courses = [];
  37. const entries = cellStr.split('<br/>');
  38. for (const entry of entries) {
  39. const trimmed = entry.trim();
  40. if (!trimmed) continue;
  41. // 格式: 课程名◇教师[周次]教室[节次]节
  42. // 先找◇分割课程名和教师
  43. const teacherSplit = trimmed.split('\u25C7');
  44. if (teacherSplit.length < 2) {
  45. console.log("HIT调试: 无◇分隔, text=" + trimmed.substring(0, 40));
  46. continue;
  47. }
  48. const courseName = teacherSplit[0].trim();
  49. const rest = teacherSplit.slice(1).join('\u25C7').trim();
  50. // 从rest中提取: 教师[周次]教室[节次]节
  51. // 找第一个[之前的是教师
  52. const firstBracket = rest.indexOf('[');
  53. if (firstBracket === -1) continue;
  54. const teacher = rest.substring(0, firstBracket).trim();
  55. // 提取周次: [周次] 中的内容
  56. const weekMatch = rest.match(/\[(\d+[-~,,\d]+)周\]/);
  57. if (!weekMatch) {
  58. console.log("HIT调试: 无周次, rest=" + rest.substring(0, 50));
  59. continue;
  60. }
  61. const weeksStr = weekMatch[1];
  62. const weeks = parseWeeks(weeksStr);
  63. if (weeks.length === 0) continue;
  64. // 提取节次: 最后一个[节次]节 中的内容
  65. const sectionMatch = rest.match(/\[(.+)\]节/);
  66. if (!sectionMatch) {
  67. console.log("HIT调试: 无节次, rest=" + rest.substring(0, 50));
  68. continue;
  69. }
  70. const sectionStr = sectionMatch[1].trim();
  71. // 提取教室: 周次]和节次[之间的内容
  72. const afterWeek = rest.substring(rest.indexOf(']周]') + 3);
  73. const beforeSection = afterWeek.substring(0, afterWeek.lastIndexOf('['));
  74. const room = beforeSection.trim();
  75. const sectionParts = sectionStr.split(/[,,\s]+/);
  76. const startSection = parseInt(sectionParts[0], 10);
  77. const endSection = sectionParts.length > 1 ? parseInt(sectionParts[sectionParts.length - 1], 10) : startSection;
  78. if (isNaN(startSection) || isNaN(endSection)) {
  79. console.log("HIT调试: 节次解析失败, sectionStr=" + sectionStr);
  80. continue;
  81. }
  82. console.log("HIT调试: 解析成功, " + courseName + "|" + teacher + "|" + room + "|d" + day + "|s" + startSection + "-" + endSection + "|w" + weeks.length);
  83. courses.push({
  84. name: courseName,
  85. teacher: teacher,
  86. position: room,
  87. day: day,
  88. startSection: startSection,
  89. endSection: endSection,
  90. weeks: weeks
  91. });
  92. }
  93. return courses;
  94. }
  95. function mergeCourses(courses) {
  96. if (courses.length <= 1) return courses;
  97. courses.sort((a, b) => {
  98. return a.name.localeCompare(b.name) || a.teacher.localeCompare(b.teacher) ||
  99. a.position.localeCompare(b.position) || (a.day || 0) - (b.day || 0) ||
  100. (a.startSection || 0) - (b.startSection || 0);
  101. });
  102. const merged = [];
  103. let cur = courses[0];
  104. for (let i = 1; i < courses.length; i++) {
  105. const nxt = courses[i];
  106. if (cur.name === nxt.name && cur.teacher === nxt.teacher && cur.position === nxt.position &&
  107. cur.day === nxt.day && cur.startSection === nxt.startSection && cur.endSection === nxt.endSection) {
  108. cur.weeks = Array.from(new Set([...cur.weeks, ...nxt.weeks])).sort((a, b) => a - b);
  109. } else {
  110. merged.push(cur);
  111. cur = nxt;
  112. }
  113. }
  114. merged.push(cur);
  115. return merged;
  116. }
  117. async function promptUserToStart() {
  118. return await window.shiguangBridgePromise.showAlert(
  119. "研究生课表导入",
  120. "导入前请确保您已在浏览器中成功登录研究生系统",
  121. "好的,开始导入"
  122. );
  123. }
  124. async function fetchCurrentSchedule() {
  125. const url = "/xs/index/getDqxqkb?sf_request_type=ajax";
  126. try {
  127. const response = await fetch(url, {
  128. method: "POST",
  129. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  130. credentials: "include"
  131. });
  132. if (!response.ok) return null;
  133. const data = await response.json();
  134. if (!data.isSuccess || !data.module) return null;
  135. return data;
  136. } catch (e) {
  137. return null;
  138. }
  139. }
  140. async function fetchSemesterInfo() {
  141. const url = "/xs/index/getZcxx?sf_request_type=ajax";
  142. try {
  143. const response = await fetch(url, {
  144. method: "POST",
  145. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  146. credentials: "include"
  147. });
  148. if (!response.ok) return null;
  149. const data = await response.json();
  150. if (!data.isSuccess || !data.module) return null;
  151. return data.module;
  152. } catch (e) {
  153. return null;
  154. }
  155. }
  156. const dayMap = { mon: 1, tues: 2, wed: 3, thur: 4, fri: 5, sat: 6, sun: 7 };
  157. function parseScheduleData(scheduleData) {
  158. const allCourses = [];
  159. const module = scheduleData.module;
  160. console.log("HIT调试: module行数=" + module.length);
  161. for (const row of module) {
  162. const sections = row.jcmc;
  163. for (const [dayKey, dayNum] of Object.entries(dayMap)) {
  164. const cellStr = row[dayKey];
  165. if (!cellStr || cellStr === 'null') continue;
  166. console.log("HIT调试: " + dayKey + "=" + cellStr.substring(0, 50));
  167. const courses = parseCourseCell(cellStr, dayNum, sections);
  168. console.log("HIT调试: 解析出" + courses.length + "门课");
  169. allCourses.push(...courses);
  170. }
  171. }
  172. return mergeCourses(allCourses);
  173. }
  174. async function saveCourses(courses) {
  175. try {
  176. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  177. return true;
  178. } catch (error) {
  179. window.shiguangBridge.showToast("课程保存失败: " + error.message);
  180. return false;
  181. }
  182. }
  183. async function runImportFlow() {
  184. const alertConfirmed = await promptUserToStart();
  185. if (!alertConfirmed) {
  186. window.shiguangBridge.showToast("用户取消了导入。");
  187. return;
  188. }
  189. window.shiguangBridge.showToast("正在获取课表数据...");
  190. const [scheduleData, semesterInfo] = await Promise.all([
  191. fetchCurrentSchedule(),
  192. fetchSemesterInfo()
  193. ]);
  194. if (!scheduleData) {
  195. window.shiguangBridge.showToast("未能获取课表数据,请检查网络环境或登录状态。");
  196. return;
  197. }
  198. console.log("HIT调试: API返回成功, module长度=" + scheduleData.module.length);
  199. console.log("HIT调试: 第一行数据=" + JSON.stringify(scheduleData.module[0]));
  200. const courses = parseScheduleData(scheduleData);
  201. console.log("HIT调试: 总共解析=" + courses.length + "门课");
  202. if (courses.length > 0) {
  203. console.log("HIT调试: 第一门=" + JSON.stringify(courses[0]));
  204. }
  205. if (courses.length === 0) {
  206. window.shiguangBridge.showToast("未找到课程数据");
  207. return;
  208. }
  209. const saveResult = await saveCourses(courses);
  210. if (!saveResult) return;
  211. if (semesterInfo && semesterInfo.ZCJSSJ) {
  212. try {
  213. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({
  214. semesterStartDate: null,
  215. semesterTotalWeeks: 20
  216. }));
  217. } catch (e) {}
  218. }
  219. const semesterName = semesterInfo ? semesterInfo.MC : "当前学期";
  220. window.shiguangBridge.showToast("课程导入成功!" + semesterName + ",共 " + courses.length + " 门课程");
  221. window.shiguangBridge.notifyTaskCompletion();
  222. }
  223. runImportFlow();