XAUT_01.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /**
  2. * 辅助函数:解析强智系统的周次字符串
  3. */
  4. function parseWeeks(weekStr) {
  5. if (!weekStr) return [];
  6. let weeks = [];
  7. let parts = weekStr.split(',');
  8. for (let part of parts) {
  9. if (part.includes('-')) {
  10. let [start, end] = part.split('-');
  11. for (let i = parseInt(start); i <= parseInt(end); i++) {
  12. if (!weeks.includes(i)) weeks.push(i);
  13. }
  14. } else {
  15. let w = parseInt(part);
  16. if (!isNaN(w) && !weeks.includes(w)) weeks.push(w);
  17. }
  18. }
  19. return weeks.sort((a, b) => a - b);
  20. }
  21. /**
  22. * 核心解析函数:提取并构建“大节”时间轴,同时提取课程并映射去重
  23. */
  24. function extractDataFromDoc(doc) {
  25. let timeSlots = [];
  26. let sectionMapping = {}; // 记录 小节->大节 的映射
  27. let rowToSectionMap = {}; // 记录 表格行号->大节 的映射
  28. let newSectionIndex = 1;
  29. const table = doc.getElementById('timetable');
  30. if (!table) throw new Error("请求成功但未找到课表表格,请确认教务系统状态。");
  31. const rows = table.getElementsByTagName('tr');
  32. // ==========================================
  33. // 1. 解析时间轴,剔除午休,建立小节到大节的映射
  34. // ==========================================
  35. for (let i = 1; i < rows.length - 1; i++) {
  36. let th = rows[i].querySelector('th');
  37. if (!th) continue;
  38. let html = th.innerHTML;
  39. let secMatch = html.match(/\(([\d,]+)小节\)/);
  40. let timeMatch = html.match(/(\d{2}:\d{2})-(\d{2}:\d{2})/);
  41. if (secMatch && timeMatch) {
  42. let startStr = timeMatch[1];
  43. let endStr = timeMatch[2];
  44. // 【核心要求】剔除 12:10-14:00 这个无效的午休时间段
  45. if (startStr === "12:10" && endStr === "14:00") {
  46. continue; // 跳过,不计入真实上课时间轴
  47. }
  48. // 记录真实上课的“大节”
  49. timeSlots.push({
  50. number: newSectionIndex,
  51. startTime: startStr,
  52. endTime: endStr
  53. });
  54. // 绑定该行对应的大节索引
  55. rowToSectionMap[i] = newSectionIndex;
  56. // 将 (07,08小节) 这样的小节数字,统一映射到新生成的大节索引上
  57. let smallSections = secMatch[1].split(',').map(s => parseInt(s, 10));
  58. smallSections.forEach(s => {
  59. sectionMapping[s] = newSectionIndex;
  60. });
  61. newSectionIndex++;
  62. }
  63. }
  64. // ==========================================
  65. // 2. 逐行提取课程内容,并将其映射到大节上
  66. // ==========================================
  67. let parsedCourses = [];
  68. for (let i = 1; i < rows.length - 1; i++) {
  69. let targetSection = rowToSectionMap[i];
  70. if (!targetSection) continue; // 如果这行是午休那行,直接跳过处理
  71. const cells = rows[i].getElementsByTagName('td');
  72. for (let j = 0; j < cells.length; j++) {
  73. const dayOfWeek = j + 1;
  74. const cell = cells[j];
  75. const detailDivs = cell.querySelectorAll('div.kbcontent');
  76. if (detailDivs.length === 0) continue;
  77. detailDivs.forEach(div => {
  78. let htmlContent = div.innerHTML;
  79. if (!htmlContent.trim() || htmlContent === '&nbsp;') return;
  80. let courseBlocks = htmlContent.split(/-{10,}\s*<br\s*\/?>/i);
  81. courseBlocks.forEach(block => {
  82. if (!block.trim()) return;
  83. let tempDiv = document.createElement('div');
  84. tempDiv.innerHTML = block;
  85. let courseObj = {
  86. day: dayOfWeek,
  87. isCustomTime: false
  88. };
  89. // 提取课程名 (剥离二维码 item-box 节点)
  90. let itemBoxes = tempDiv.querySelectorAll('.item-box');
  91. itemBoxes.forEach(box => box.remove());
  92. let lines = tempDiv.innerHTML.split(/<br\s*\/?>/i);
  93. for (let line of lines) {
  94. let cleanLine = line.replace(/<[^>]+>/g, '').trim();
  95. if (cleanLine && cleanLine !== "") {
  96. courseObj.name = cleanLine;
  97. break;
  98. }
  99. }
  100. // 提取教师和教室
  101. let teacherFont = tempDiv.querySelector('font[title="教师"]');
  102. courseObj.teacher = teacherFont ? teacherFont.innerText.trim() : "未知";
  103. let positionFont = tempDiv.querySelector('font[title="教室"]');
  104. courseObj.position = positionFont ? positionFont.innerText.trim() : "待定";
  105. // 提取并转换周次、节次
  106. let timeFont = tempDiv.querySelector('font[title="周次(节次)"]');
  107. if (timeFont) {
  108. let timeText = timeFont.innerText.trim();
  109. let timeMatch = timeText.match(/(.+?)\(周\)(?:\[([\d-]+)节\])?/);
  110. if (timeMatch) {
  111. courseObj.weeks = parseWeeks(timeMatch[1]);
  112. if (timeMatch[2]) {
  113. let secParts = timeMatch[2].split('-');
  114. let origStart = parseInt(secParts[0]);
  115. let origEnd = parseInt(secParts[secParts.length - 1]);
  116. // 【核心转换】将原始的 07, 10 等小节,转换为 App 里的 3,4 等大节
  117. courseObj.startSection = sectionMapping[origStart];
  118. courseObj.endSection = sectionMapping[origEnd];
  119. } else {
  120. courseObj.startSection = targetSection;
  121. courseObj.endSection = targetSection;
  122. }
  123. }
  124. } else {
  125. return; // 抛弃无时间信息的无课表课程
  126. }
  127. if (courseObj.name && courseObj.weeks && courseObj.weeks.length > 0 && courseObj.startSection && courseObj.endSection) {
  128. parsedCourses.push(courseObj);
  129. }
  130. });
  131. });
  132. }
  133. }
  134. // ==========================================
  135. // 3. 终极去重(处理跨大节的连排课导致的 DOM 重复)
  136. // ==========================================
  137. let uniqueCourses = [];
  138. let courseSet = new Set();
  139. parsedCourses.forEach(course => {
  140. let uniqueKey = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.weeks.join(',')}`;
  141. if (!courseSet.has(uniqueKey)) {
  142. courseSet.add(uniqueKey);
  143. uniqueCourses.push(course);
  144. }
  145. });
  146. return { timeSlots, uniqueCourses };
  147. }
  148. /**
  149. * 异步编排流程
  150. */
  151. async function runImportFlow() {
  152. try {
  153. if (typeof window.AndroidBridge !== 'undefined') {
  154. AndroidBridge.showToast("正在获取课表与作息配置...");
  155. } else {
  156. console.log("正在发起请求获取课表...");
  157. }
  158. // 第 1 步:请求外层页面,获取学期列表
  159. const response = await fetch('/jsxsd/xskb/xskb_list.do', { method: 'GET' });
  160. const htmlText = await response.text();
  161. const parser = new DOMParser();
  162. let doc = parser.parseFromString(htmlText, 'text/html');
  163. const selectElem = doc.getElementById('xnxq01id');
  164. let semesters = [];
  165. let semesterValues = [];
  166. let defaultIndex = 0;
  167. if (selectElem) {
  168. const options = selectElem.querySelectorAll('option');
  169. options.forEach((opt, index) => {
  170. semesters.push(opt.innerText.trim());
  171. semesterValues.push(opt.value);
  172. if (opt.hasAttribute('selected')) {
  173. defaultIndex = index;
  174. }
  175. });
  176. }
  177. // 第 2 步:选择学期
  178. let selectedIdx = defaultIndex;
  179. if (semesters.length > 0 && typeof window.AndroidBridgePromise !== 'undefined') {
  180. let userChoice = await window.AndroidBridgePromise.showSingleSelection(
  181. "请选择要导入的学期",
  182. JSON.stringify(semesters),
  183. defaultIndex
  184. );
  185. if (userChoice === null) {
  186. AndroidBridge.showToast("已取消导入");
  187. return;
  188. }
  189. selectedIdx = userChoice;
  190. // 如果非默认学期,重新获取页面
  191. if (selectedIdx !== defaultIndex) {
  192. AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 课表...`);
  193. let formData = new URLSearchParams();
  194. formData.append('xnxq01id', semesterValues[selectedIdx]);
  195. const postResponse = await fetch('/jsxsd/xskb/xskb_list.do', {
  196. method: 'POST',
  197. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  198. body: formData.toString()
  199. });
  200. const postHtml = await postResponse.text();
  201. doc = parser.parseFromString(postHtml, 'text/html');
  202. }
  203. } else if (typeof window.AndroidBridgePromise === 'undefined') {
  204. // 浏览器环境测验
  205. let msg = "【浏览器测试】请选择学期序号:\n\n";
  206. semesters.forEach((s, idx) => msg += `[${idx}] : ${s}\n`);
  207. let userInput = prompt(msg, defaultIndex);
  208. if (userInput === null) return;
  209. selectedIdx = parseInt(userInput);
  210. if (isNaN(selectedIdx)) selectedIdx = defaultIndex;
  211. if (selectedIdx !== defaultIndex) {
  212. let formData = new URLSearchParams();
  213. formData.append('xnxq01id', semesterValues[selectedIdx]);
  214. const postResponse = await fetch('/jsxsd/xskb/xskb_list.do', {
  215. method: 'POST',
  216. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  217. body: formData.toString()
  218. });
  219. const postHtml = await postResponse.text();
  220. doc = parser.parseFromString(postHtml, 'text/html');
  221. }
  222. }
  223. // 第 3 步:解析数据
  224. const extractedData = extractDataFromDoc(doc);
  225. const timeSlots = extractedData.timeSlots;
  226. const courses = extractedData.uniqueCourses;
  227. if (courses.length === 0) {
  228. const errMsg = "未能解析到任何课程,请检查是否暂无排课。";
  229. if (typeof window.AndroidBridgePromise !== 'undefined') {
  230. await window.AndroidBridgePromise.showAlert("提示", errMsg, "好的");
  231. } else {
  232. alert(errMsg);
  233. }
  234. return;
  235. }
  236. const config = {
  237. "defaultClassDuration": 110, // 大节课一般是 110 分钟左右
  238. "defaultBreakDuration": 10
  239. };
  240. // 浏览器测试输出
  241. if (typeof window.AndroidBridgePromise === 'undefined') {
  242. console.log("【智能抓取大节时间轴 (剔除12:10-14:00)】\n", timeSlots);
  243. console.log(`【成功提取去重课程 (${courses.length}门)】\n`, JSON.stringify(courses, null, 2));
  244. alert(`解析并去重成功!获取到 ${courses.length} 门课程及大节作息。请打开F12查看。`);
  245. return;
  246. }
  247. // APP 环境保存
  248. if (timeSlots.length > 0) {
  249. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  250. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  251. }
  252. const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  253. if (!saveResult) {
  254. AndroidBridge.showToast("保存课程失败,请重试!");
  255. return;
  256. }
  257. AndroidBridge.showToast(`成功导入 ${courses.length} 节课程及作息时间!`);
  258. AndroidBridge.notifyTaskCompletion();
  259. } catch (error) {
  260. if (typeof window.AndroidBridge !== 'undefined') {
  261. AndroidBridge.showToast("导入发生异常: " + error.message);
  262. } else {
  263. console.error("【导入发生异常】", error);
  264. alert("导入发生异常: " + error.message);
  265. }
  266. }
  267. }
  268. runImportFlow();