hit.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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 setPresetTimeSlots() {
  184. const presetTimeSlots = [
  185. { "number": 1, "startTime": "08:00", "endTime": "08:50" },
  186. { "number": 2, "startTime": "08:55", "endTime": "09:45" },
  187. { "number": 3, "startTime": "10:00", "endTime": "10:50" },
  188. { "number": 4, "startTime": "10:55", "endTime": "11:45" },
  189. { "number": 5, "startTime": "13:45", "endTime": "14:35" },
  190. { "number": 6, "startTime": "14:40", "endTime": "15:30" },
  191. { "number": 7, "startTime": "15:45", "endTime": "16:35" },
  192. { "number": 8, "startTime": "16:40", "endTime": "17:30" },
  193. { "number": 9, "startTime": "18:30", "endTime": "19:20" },
  194. { "number": 10, "startTime": "19:25", "endTime": "20:15" },
  195. { "number": 11, "startTime": "20:30", "endTime": "21:20" },
  196. { "number": 12, "startTime": "21:25", "endTime": "22:15" }
  197. ];
  198. try {
  199. const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  200. if (result === true) {
  201. console.log("HIT: 预设时间段导入成功");
  202. }
  203. } catch (error) {
  204. console.error("HIT: 导入时间段失败:", error);
  205. }
  206. }
  207. async function runImportFlow() {
  208. const alertConfirmed = await promptUserToStart();
  209. if (!alertConfirmed) {
  210. window.shiguangBridge.showToast("用户取消了导入。");
  211. return;
  212. }
  213. window.shiguangBridge.showToast("正在获取课表数据...");
  214. const [scheduleData, semesterInfo] = await Promise.all([
  215. fetchCurrentSchedule(),
  216. fetchSemesterInfo()
  217. ]);
  218. if (!scheduleData) {
  219. window.shiguangBridge.showToast("未能获取课表数据,请检查网络环境或登录状态。");
  220. return;
  221. }
  222. console.log("HIT调试: API返回成功, module长度=" + scheduleData.module.length);
  223. console.log("HIT调试: 第一行数据=" + JSON.stringify(scheduleData.module[0]));
  224. const courses = parseScheduleData(scheduleData);
  225. console.log("HIT调试: 总共解析=" + courses.length + "门课");
  226. if (courses.length > 0) {
  227. console.log("HIT调试: 第一门=" + JSON.stringify(courses[0]));
  228. }
  229. if (courses.length === 0) {
  230. window.shiguangBridge.showToast("未找到课程数据");
  231. return;
  232. }
  233. const saveResult = await saveCourses(courses);
  234. if (!saveResult) return;
  235. await setPresetTimeSlots();
  236. if (semesterInfo && semesterInfo.ZCJSSJ) {
  237. try {
  238. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({
  239. semesterStartDate: null,
  240. semesterTotalWeeks: 20
  241. }));
  242. } catch (e) {}
  243. }
  244. const semesterName = semesterInfo ? semesterInfo.MC : "当前学期";
  245. window.shiguangBridge.showToast("课程导入成功!" + semesterName + ",共 " + courses.length + " 门课程");
  246. window.shiguangBridge.notifyTaskCompletion();
  247. }
  248. runImportFlow();