haust.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. // resources/HAUST/haust.js
  2. // 河南科技大学 - 新版树维教务
  3. async function promptUserToStart() {
  4. try {
  5. var confirmed = await window.shiguangBridgePromise.showAlert(
  6. "教务系统课表导入",
  7. "请按以下步骤操作:\n\n1. 先在浏览器中登录VPN\n2. 进入教务系统\n3. 点击【我的课表】\n4. 确保课表已完整显示\n5. 然后点击下方按钮开始导入",
  8. "我已进入课表页面,开始导入"
  9. );
  10. return confirmed === true;
  11. } catch (error) {
  12. console.error("显示弹窗出错:", error);
  13. return false;
  14. }
  15. }
  16. async function getSemesterList() {
  17. try {
  18. var response = await fetch("/eams/dataQuery.action", {
  19. method: "POST",
  20. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  21. body: "dataType=semesterCalendar&tagId=semesterBar&empty=true",
  22. credentials: "include"
  23. });
  24. if (!response.ok) {
  25. throw new Error("请求失败: " + response.status);
  26. }
  27. var text = await response.text();
  28. var data = eval("(" + text + ")");
  29. if (!data || !data.semesters) {
  30. throw new Error("未找到学期数据");
  31. }
  32. var allSemesters = [];
  33. for (var key in data.semesters) {
  34. if (key.startsWith("y") && Array.isArray(data.semesters[key])) {
  35. for (var i = 0; i < data.semesters[key].length; i++) {
  36. allSemesters.push(data.semesters[key][i]);
  37. }
  38. }
  39. }
  40. if (allSemesters.length === 0) {
  41. throw new Error("学期列表为空");
  42. }
  43. allSemesters.sort(function(a, b) {
  44. var yearA = a.schoolYear.split("-")[0];
  45. var yearB = b.schoolYear.split("-")[0];
  46. if (yearB !== yearA) return parseInt(yearB) - parseInt(yearA);
  47. return parseInt(b.name) - parseInt(a.name);
  48. });
  49. var semesterTexts = allSemesters.map(function(sem) {
  50. return sem.schoolYear + "学年第" + sem.name + "学期";
  51. });
  52. var selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  53. "选择学期",
  54. JSON.stringify(semesterTexts),
  55. 0
  56. );
  57. if (selectedIndex !== null && selectedIndex >= 0) {
  58. window.shiguangBridge.showToast("已选择: " + semesterTexts[selectedIndex]);
  59. return allSemesters[selectedIndex];
  60. }
  61. return null;
  62. } catch (error) {
  63. console.error("获取学期出错:", error);
  64. window.shiguangBridge.showToast("获取学期出错: " + error.message);
  65. return null;
  66. }
  67. }
  68. function parseWeeks(weekStr) {
  69. if (!weekStr) return [];
  70. var cleaned = weekStr.replace(/周/g, "").trim();
  71. if (!cleaned) return [];
  72. var weeks = [];
  73. var parts = cleaned.split(",");
  74. for (var p = 0; p < parts.length; p++) {
  75. var trimmed = parts[p].trim();
  76. if (!trimmed) continue;
  77. var isOdd = trimmed.charAt(0) === "单";
  78. var isEven = trimmed.charAt(0) === "双";
  79. var rangeStr = isOdd || isEven ? trimmed.substring(1) : trimmed;
  80. var ranges = rangeStr.split(/\s+/);
  81. for (var r = 0; r < ranges.length; r++) {
  82. var range = ranges[r];
  83. var rangeMatch = range.match(/^(\d+)\s*[-~]\s*(\d+)$/);
  84. if (rangeMatch) {
  85. var start = parseInt(rangeMatch[1], 10);
  86. var end = parseInt(rangeMatch[2], 10);
  87. for (var w = start; w <= end; w++) {
  88. if (isOdd && w % 2 === 0) continue;
  89. if (isEven && w % 2 === 1) continue;
  90. weeks.push(w);
  91. }
  92. } else {
  93. var num = parseInt(range, 10);
  94. if (!isNaN(num)) {
  95. if (isOdd && num % 2 === 0) continue;
  96. if (isEven && num % 2 === 1) continue;
  97. weeks.push(num);
  98. }
  99. }
  100. }
  101. }
  102. weeks.sort(function(a, b) { return a - b; });
  103. var unique = [];
  104. var seen = {};
  105. for (var i = 0; i < weeks.length; i++) {
  106. if (!seen[weeks[i]]) {
  107. unique.push(weeks[i]);
  108. seen[weeks[i]] = true;
  109. }
  110. }
  111. return unique;
  112. }
  113. function parsePeriod(periodStr) {
  114. if (!periodStr) return null;
  115. var match = periodStr.match(/(\d+)\s*[-~]\s*(\d+)/);
  116. if (match) {
  117. return { start: parseInt(match[1], 10), end: parseInt(match[2], 10) };
  118. }
  119. return null;
  120. }
  121. function stripHtml(html) {
  122. if (!html) return "";
  123. return html.replace(/<[^>]+>/g, "").trim();
  124. }
  125. function parseTitle(title) {
  126. if (!title) return [];
  127. var courses = [];
  128. var cleanTitle = stripHtml(title);
  129. // 格式: "课程名(课程号) (教师);;;(周次,节次,教室)"
  130. // 先提取课程名、课程号、教师
  131. var nameMatch = cleanTitle.match(/^(.+?)\(([^)]+)\)\s*\(([^)]+)\)/);
  132. if (!nameMatch) return [];
  133. var courseName = nameMatch[1].trim();
  134. var teacher = nameMatch[3].trim();
  135. // 提取周次、节次、教室 - 找到含"周"的括号内容,手动提取到最后一个")"
  136. var detailStart = cleanTitle.search(/\(\d+[-~]\d+周/);
  137. if (detailStart === -1) return [];
  138. var lastParen = cleanTitle.lastIndexOf(")");
  139. if (lastParen <= detailStart) return [];
  140. var details = cleanTitle.substring(detailStart + 1, lastParen);
  141. var parts = details.split(",");
  142. var weeksStr = parts.length >= 1 ? parts[0].trim() : "";
  143. var periodStr = parts.length >= 2 ? parts[1].trim() : "";
  144. var room = parts.length >= 3 ? parts.slice(2).join(",").trim() : "";
  145. var weeks = parseWeeks(weeksStr);
  146. var period = parsePeriod(periodStr);
  147. if (weeks.length > 0 && period) {
  148. courses.push({
  149. courseName: courseName,
  150. teacher: teacher,
  151. weeks: weeks,
  152. startSection: period.start,
  153. endSection: period.end,
  154. room: room
  155. });
  156. }
  157. return courses;
  158. }
  159. function tryFindCourseTable() {
  160. // 方法1: 当前页面直接查找
  161. var cells = document.querySelectorAll("td[title]");
  162. if (cells.length === 0) cells = document.querySelectorAll("td[id^='TD']");
  163. if (cells.length > 0) {
  164. return { doc: document, cells: cells, source: "current" };
  165. }
  166. // 方法2: iframe
  167. var iframes = document.querySelectorAll("iframe");
  168. for (var i = 0; i < iframes.length; i++) {
  169. try {
  170. var iframeDoc = iframes[i].contentDocument || iframes[i].contentWindow.document;
  171. if (!iframeDoc) continue;
  172. cells = iframeDoc.querySelectorAll("td[title]");
  173. if (cells.length === 0) cells = iframeDoc.querySelectorAll("td[id^='TD']");
  174. if (cells.length > 0) {
  175. return { doc: iframeDoc, cells: cells, source: "iframe" };
  176. }
  177. // 方法3: iframe的iframe
  178. var innerIframes = iframeDoc.querySelectorAll("iframe");
  179. for (var j = 0; j < innerIframes.length; j++) {
  180. try {
  181. var innerDoc = innerIframes[j].contentDocument || innerIframes[j].contentWindow.document;
  182. if (!innerDoc) continue;
  183. cells = innerDoc.querySelectorAll("td[title]");
  184. if (cells.length === 0) cells = innerDoc.querySelectorAll("td[id^='TD']");
  185. if (cells.length > 0) {
  186. return { doc: innerDoc, cells: cells, source: "inner-iframe" };
  187. }
  188. } catch (e) {}
  189. }
  190. } catch (e) {}
  191. }
  192. return null;
  193. }
  194. function parseCoursesFromTable(tableData) {
  195. var allCourses = [];
  196. var seenKeys = {};
  197. var cells = tableData.cells;
  198. for (var i = 0; i < cells.length; i++) {
  199. var cell = cells[i];
  200. var title = cell.getAttribute("title");
  201. if (!title || !title.trim()) continue;
  202. var row = cell.parentElement;
  203. if (!row) continue;
  204. var rowCells = Array.from(row.cells);
  205. var colIndex = rowCells.indexOf(cell);
  206. var day = colIndex;
  207. if (day < 1 || day > 7) continue;
  208. var parsed = parseTitle(title);
  209. for (var j = 0; j < parsed.length; j++) {
  210. var course = parsed[j];
  211. if (course.weeks.length === 0 || course.startSection === 0) continue;
  212. var key = course.courseName + "_" + day + "_" + course.startSection + "_" + course.weeks.join(",");
  213. if (seenKeys[key]) continue;
  214. seenKeys[key] = true;
  215. allCourses.push({
  216. name: course.courseName,
  217. teacher: course.teacher,
  218. position: course.room,
  219. day: day,
  220. startSection: course.startSection,
  221. endSection: course.endSection,
  222. weeks: course.weeks
  223. });
  224. }
  225. }
  226. return allCourses;
  227. }
  228. async function saveCourses(courses) {
  229. try {
  230. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  231. return true;
  232. } catch (error) {
  233. window.shiguangBridge.showToast("课程保存失败: " + error.message);
  234. return false;
  235. }
  236. }
  237. async function runImportFlow() {
  238. window.shiguangBridge.showToast("开始导入...");
  239. var alertConfirmed = await promptUserToStart();
  240. if (!alertConfirmed) {
  241. window.shiguangBridge.showToast("已取消导入");
  242. return;
  243. }
  244. window.shiguangBridge.showToast("正在查找课表...");
  245. var tableData = tryFindCourseTable();
  246. if (!tableData) {
  247. window.shiguangBridge.showToast("未找到课表,请确保已进入【我的课表】页面");
  248. return;
  249. }
  250. window.shiguangBridge.showToast("找到课表 (" + tableData.source + "),共 " + tableData.cells.length + " 个单元格");
  251. // 调试:输出前几个单元格信息
  252. var debugInfo = [];
  253. for (var d = 0; d < Math.min(5, tableData.cells.length); d++) {
  254. var dc = tableData.cells[d];
  255. var dt = dc.getAttribute("title") || "";
  256. var dr = dc.parentElement;
  257. var dci = -1;
  258. if (dr) {
  259. var drc = Array.from(dr.cells);
  260. dci = drc.indexOf(dc);
  261. }
  262. debugInfo.push(dc.id + "|col:" + dci + "|day:" + dci + "|title:" + dt.substring(0, 80));
  263. }
  264. console.log("HAUST调试: " + debugInfo.join(" || "));
  265. var courses = parseCoursesFromTable(tableData);
  266. if (courses.length === 0) {
  267. // 输出解析失败的调试信息
  268. var failDebug = [];
  269. for (var f = 0; f < Math.min(3, tableData.cells.length); f++) {
  270. var fc = tableData.cells[f];
  271. var ft = fc.getAttribute("title") || "";
  272. var fr = fc.parentElement;
  273. var fci = -1;
  274. if (fr) {
  275. var frc = Array.from(fr.cells);
  276. fci = frc.indexOf(fc);
  277. }
  278. var parsed = parseTitle(ft);
  279. failDebug.push("col:" + fci + "|parsed:" + parsed.length + "|raw:" + ft.substring(0, 60));
  280. }
  281. window.shiguangBridge.showToast("解析失败: " + failDebug[0]);
  282. }
  283. if (courses.length === 0) {
  284. window.shiguangBridge.showToast("未解析到课程,请检查课表是否完整显示");
  285. return;
  286. }
  287. var saveResult = await saveCourses(courses);
  288. if (!saveResult) return;
  289. window.shiguangBridge.showToast("导入成功!共 " + courses.length + " 门课程");
  290. window.shiguangBridge.notifyTaskCompletion();
  291. }
  292. runImportFlow();