HNUST_01.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /**
  2. * 解析强智系统的周次字符串
  3. */
  4. function parseWeeks(weekStr) {
  5. let weeks = [];
  6. let parts = weekStr.split(',');
  7. for (let part of parts) {
  8. if (part.includes('-')) {
  9. let [start, end] = part.split('-');
  10. for (let i = parseInt(start); i <= parseInt(end); i++) {
  11. if (!weeks.includes(i)) weeks.push(i);
  12. }
  13. } else {
  14. let w = parseInt(part);
  15. if (!isNaN(w) && !weeks.includes(w)) weeks.push(w);
  16. }
  17. }
  18. return weeks.sort((a, b) => a - b);
  19. }
  20. /**
  21. * 提取课程数据
  22. */
  23. /**
  24. * 提取课程数据(已优化多节连堂课合并与去重逻辑)
  25. */
  26. function extractCoursesFromDoc(doc) {
  27. let parsedCourses = [];
  28. const table = doc.getElementById('timetable');
  29. if (!table) throw new Error("请求成功但未找到课表表格,请确认教务系统状态。");
  30. const rows = table.getElementsByTagName('tr');
  31. for (let i = 1; i < rows.length - 1; i++) {
  32. const cells = rows[i].getElementsByTagName('td');
  33. for (let j = 0; j < cells.length; j++) {
  34. const dayOfWeek = j + 1;
  35. const cell = cells[j];
  36. const detailDivs = cell.querySelectorAll('div.kbcontent');
  37. if (detailDivs.length === 0) continue;
  38. detailDivs.forEach(div => {
  39. let htmlContent = div.innerHTML;
  40. if (!htmlContent.trim() || htmlContent === '&nbsp;') return;
  41. let courseBlocks = htmlContent.split(/-{10,}\s*<br\s*\/?>/i);
  42. courseBlocks.forEach(block => {
  43. if (!block.trim()) return;
  44. let tempDiv = document.createElement('div');
  45. tempDiv.innerHTML = block;
  46. let courseObj = {
  47. day: dayOfWeek,
  48. isCustomTime: false
  49. };
  50. let firstNode = tempDiv.childNodes[0];
  51. if (firstNode && firstNode.nodeType === Node.TEXT_NODE) {
  52. courseObj.name = firstNode.nodeValue.trim();
  53. } else {
  54. courseObj.name = tempDiv.innerText.split('\n')[0].trim();
  55. }
  56. let teacherFont = tempDiv.querySelector('font[title="教师"]');
  57. courseObj.teacher = teacherFont ? teacherFont.innerText.trim() : "未知";
  58. let positionFont = tempDiv.querySelector('font[title="教室"]');
  59. courseObj.position = positionFont ? positionFont.innerText.trim() : "待定";
  60. let timeFont = tempDiv.querySelector('font[title="周次(节次)"]');
  61. if (timeFont) {
  62. let timeText = timeFont.innerText.trim();
  63. let timeMatch = timeText.match(/(.+?)\(周\)\[([\d-]+)节\]/);
  64. if (timeMatch) {
  65. courseObj.weeks = parseWeeks(timeMatch[1]);
  66. let sections = timeMatch[2].split('-');
  67. courseObj.startSection = parseInt(sections[0]);
  68. courseObj.endSection = parseInt(sections[sections.length - 1]);
  69. } else {
  70. let weekOnlyMatch = timeText.match(/(.+?)\(周\)/);
  71. if (weekOnlyMatch) {
  72. courseObj.weeks = parseWeeks(weekOnlyMatch[1]);
  73. courseObj.startSection = i * 2 - 1;
  74. courseObj.endSection = i * 2;
  75. } else return;
  76. }
  77. } else return;
  78. if (courseObj.name && courseObj.weeks && courseObj.weeks.length > 0) {
  79. let isDuplicate = parsedCourses.some(c =>
  80. c.day === courseObj.day &&
  81. c.name === courseObj.name &&
  82. c.startSection === courseObj.startSection &&
  83. c.endSection === courseObj.endSection &&
  84. c.teacher === courseObj.teacher &&
  85. c.position === courseObj.position &&
  86. JSON.stringify(c.weeks) === JSON.stringify(courseObj.weeks)
  87. );
  88. if (!isDuplicate) {
  89. parsedCourses.push(courseObj);
  90. }
  91. }
  92. });
  93. });
  94. }
  95. }
  96. return parsedCourses;
  97. }
  98. // ======== 替换原有的 getPresetTimeSlots,引入双套作息时间 ========
  99. // 非夏季(秋冬春)作息(保持 HNUST 原有数据)
  100. const Non_summerTimeSlots = [
  101. { "number": 1, "startTime": "08:00", "endTime": "08:45" },
  102. { "number": 2, "startTime": "08:55", "endTime": "09:40" },
  103. { "number": 3, "startTime": "10:00", "endTime": "10:45" },
  104. { "number": 4, "startTime": "10:55", "endTime": "11:40" },
  105. { "number": 5, "startTime": "14:00", "endTime": "14:45" },
  106. { "number": 6, "startTime": "14:55", "endTime": "15:40" },
  107. { "number": 7, "startTime": "16:00", "endTime": "16:45" },
  108. { "number": 8, "startTime": "16:55", "endTime": "17:40" },
  109. { "number": 9, "startTime": "19:00", "endTime": "19:45" },
  110. { "number": 10,"startTime": "19:55", "endTime": "20:40" }
  111. ];
  112. // 夏季作息(注:此处假设下午推迟半小时,请根据 HNUST 实际情况微调时间)
  113. const SummerTimeSlots = [
  114. { "number": 1, "startTime": "08:00", "endTime": "08:45" },
  115. { "number": 2, "startTime": "08:55", "endTime": "09:40" },
  116. { "number": 3, "startTime": "10:00", "endTime": "10:45" },
  117. { "number": 4, "startTime": "10:55", "endTime": "11:40" },
  118. { "number": 5, "startTime": "14:30", "endTime": "15:15" },
  119. { "number": 6, "startTime": "15:25", "endTime": "16:10" },
  120. { "number": 7, "startTime": "16:30", "endTime": "17:15" },
  121. { "number": 8, "startTime": "17:25", "endTime": "18:10" },
  122. { "number": 9, "startTime": "19:30", "endTime": "20:15" },
  123. { "number": 10,"startTime": "20:25", "endTime": "21:10" }
  124. ];
  125. /**
  126. * 弹出选择作息时间
  127. */
  128. async function selectTimeSlotsType() {
  129. const timeSlotsOptions = ["非夏季作息 (14:00上课)", "夏季作息 (14:30上课)"];
  130. console.log("JS: 提示用户选择作息时间类型。");
  131. // 如果不在APP内(网页测试环境),默认返回0
  132. if (typeof window.AndroidBridgePromise === 'undefined') {
  133. return 0;
  134. }
  135. const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
  136. "选择作息时间",
  137. JSON.stringify(timeSlotsOptions),
  138. 0 // 默认选中第一个
  139. );
  140. return selectedIndex;
  141. }
  142. // =================================================================
  143. /**
  144. * 生成全局课表配置
  145. */
  146. function getCourseConfig() {
  147. return {
  148. "defaultClassDuration": 45,
  149. "defaultBreakDuration": 10
  150. };
  151. }
  152. /**
  153. * 异步编排流程
  154. */
  155. async function runImportFlow() {
  156. try {
  157. if (typeof window.AndroidBridge !== 'undefined') {
  158. AndroidBridge.showToast("正在获取课表数据,请稍候...");
  159. } else {
  160. console.log("正在发起请求获取课表...");
  161. }
  162. const response = await fetch('/jsxsd/xskb/xskb_list.do', { method: 'GET' });
  163. const htmlText = await response.text();
  164. const parser = new DOMParser();
  165. let doc = parser.parseFromString(htmlText, 'text/html');
  166. const selectElem = doc.getElementById('xnxq01id');
  167. let semesters = [];
  168. let semesterValues = [];
  169. let defaultIndex = 0;
  170. if (selectElem) {
  171. const options = selectElem.querySelectorAll('option');
  172. options.forEach((opt, index) => {
  173. semesters.push(opt.innerText.trim());
  174. semesterValues.push(opt.value);
  175. if (opt.hasAttribute('selected')) {
  176. defaultIndex = index;
  177. }
  178. });
  179. }
  180. if (semesters.length > 0 && typeof window.AndroidBridgePromise !== 'undefined') {
  181. let selectedIdx = await window.AndroidBridgePromise.showSingleSelection(
  182. "请选择要导入的学期",
  183. JSON.stringify(semesters),
  184. defaultIndex
  185. );
  186. if (selectedIdx === null) {
  187. AndroidBridge.showToast("已取消导入");
  188. return;
  189. }
  190. if (selectedIdx !== defaultIndex) {
  191. AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 课表...`);
  192. let formData = new URLSearchParams();
  193. formData.append('xnxq01id', semesterValues[selectedIdx]);
  194. const postResponse = await fetch('/jsxsd/xskb/xskb_list.do', {
  195. method: 'POST',
  196. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  197. body: formData.toString()
  198. });
  199. const postHtml = await postResponse.text();
  200. doc = parser.parseFromString(postHtml, 'text/html');
  201. }
  202. }
  203. const courses = extractCoursesFromDoc(doc);
  204. if (courses.length === 0) {
  205. const errMsg = "未能解析到任何课程,请检查是否暂无排课。";
  206. if (typeof window.AndroidBridgePromise !== 'undefined') {
  207. await window.AndroidBridgePromise.showAlert("提示", errMsg, "好的");
  208. } else {
  209. alert(errMsg);
  210. }
  211. return;
  212. }
  213. const config = getCourseConfig();
  214. // ------------------ 选择作息时间阶段 ------------------
  215. const timeSlotsIndex = await selectTimeSlotsType();
  216. if (timeSlotsIndex === null && typeof window.AndroidBridgePromise !== 'undefined') {
  217. AndroidBridge.showToast("已取消选择作息时间,终止导入");
  218. return;
  219. }
  220. let selectedTimeSlots = Non_summerTimeSlots;
  221. if (timeSlotsIndex === 1) {
  222. selectedTimeSlots = SummerTimeSlots;
  223. }
  224. // -----------------------------------------------------
  225. // 浏览器测试环境,直接输出结果
  226. if (typeof window.AndroidBridgePromise === 'undefined') {
  227. console.log("【测试成功】课表配置:", config);
  228. console.log("【测试成功】作息时间:", selectedTimeSlots);
  229. console.log("【测试成功】课程数据:", courses);
  230. alert(`解析成功!获取到 ${courses.length} 门课程以及作息时间。请打开F12控制台查看。`);
  231. return;
  232. }
  233. // APP 环境,执行保存配置和作息时间
  234. const configSaved = await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  235. const timeSlotsSaved = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(selectedTimeSlots));
  236. if (!configSaved || !timeSlotsSaved) {
  237. AndroidBridge.showToast("保存课表时间配置失败!");
  238. // 注意:时间配置失败不一定阻断课程导入,这里选择继续导入课程
  239. }
  240. // APP 环境,执行保存课程
  241. const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  242. if (!saveResult) {
  243. AndroidBridge.showToast("保存课程失败,请重试!");
  244. return;
  245. }
  246. AndroidBridge.showToast(`成功导入 ${courses.length} 节课程及作息时间!`);
  247. AndroidBridge.notifyTaskCompletion();
  248. } catch (error) {
  249. if (typeof window.AndroidBridge !== 'undefined') {
  250. AndroidBridge.showToast("导入发生异常: " + error.message);
  251. } else {
  252. console.error("【导入发生异常】", error);
  253. alert("导入发生异常: " + error.message);
  254. }
  255. }
  256. }
  257. runImportFlow();