JXFU_01.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /**
  2. * 解析周次字符串 (例如 "17,2,3,4,6")
  3. */
  4. function parseWeeks(weekStr) {
  5. if (!weekStr) return [];
  6. let weeks = [];
  7. let parts = String(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.trim());
  16. if (!isNaN(w) && !weeks.includes(w)) weeks.push(w);
  17. }
  18. }
  19. return weeks.sort((a, b) => a - b);
  20. }
  21. /**
  22. * 直接从 JS 变量中提取 JSON 数据
  23. */
  24. function extractCoursesFromHtml(htmlText) {
  25. let parsedCourses = [];
  26. // 使用正则表达式精准捕获 var kbxx = [...] 中的 JSON 数组
  27. let match = htmlText.match(/var\s+kbxx\s*=\s*(\[[\s\S]*?\])\s*;/);
  28. if (!match || !match[1]) {
  29. if (htmlText.includes('<table class="kb"')) {
  30. console.warn("页面结构正确但未找到课表数据,推测该学期暂未排课。");
  31. return []; // 这是一个合法的空课表
  32. }
  33. console.error("【调试HTML片段】", htmlText.substring(0, 800));
  34. throw new Error("未能从目标页面抓取到 kbxx 数据,请确认当前处于教务系统登录状态。");
  35. }
  36. let rawData = [];
  37. try {
  38. rawData = JSON.parse(match[1]);
  39. } catch (e) {
  40. throw new Error("解析课表JSON数据失败!");
  41. }
  42. rawData.forEach(item => {
  43. let courseObj = {
  44. name: item.kcmc || "未知课程",
  45. teacher: item.teaxms || "未知",
  46. position: item.jxcdmcs || "待定",
  47. day: parseInt(item.xq),
  48. isCustomTime: false
  49. };
  50. // 处理周次 (item.zcs 格式为 "18,2,3,4,7,8")
  51. if (item.zcs) {
  52. courseObj.weeks = parseWeeks(item.zcs);
  53. } else {
  54. return; // 无周次则抛弃
  55. }
  56. // 处理节次 (item.jcdm2 格式为 "05,06" 或 "10")
  57. if (item.jcdm2) {
  58. let sections = item.jcdm2.split(',').map(s => parseInt(s, 10));
  59. courseObj.startSection = sections[0];
  60. courseObj.endSection = sections[sections.length - 1];
  61. } else {
  62. return; // 无节次则抛弃
  63. }
  64. // 只有包含完整有效信息才加入
  65. if (courseObj.name && courseObj.weeks && courseObj.weeks.length > 0) {
  66. parsedCourses.push(courseObj);
  67. }
  68. });
  69. // 去重逻辑
  70. let uniqueCourses = [];
  71. let courseSet = new Set();
  72. parsedCourses.forEach(course => {
  73. let uniqueKey = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.weeks.join(',')}`;
  74. if (!courseSet.has(uniqueKey)) {
  75. courseSet.add(uniqueKey);
  76. uniqueCourses.push(course);
  77. }
  78. });
  79. return uniqueCourses;
  80. }
  81. /**
  82. * 生成学校专属的作息时间段
  83. */
  84. function getPresetTimeSlots() {
  85. return [
  86. { "number": 1, "startTime": "08:00", "endTime": "08:45" },
  87. // 课间 10分
  88. { "number": 2, "startTime": "08:55", "endTime": "09:40" },
  89. // 课间 20分 (特殊)
  90. { "number": 3, "startTime": "10:00", "endTime": "10:45" },
  91. // 课间 10分
  92. { "number": 4, "startTime": "10:55", "endTime": "11:40" },
  93. { "number": 5, "startTime": "14:00", "endTime": "14:45" },
  94. // 课间 10分
  95. { "number": 6, "startTime": "14:55", "endTime": "15:40" },
  96. // 课间 15分 (特殊)
  97. { "number": 7, "startTime": "15:55", "endTime": "16:40" },
  98. // 课间 10分
  99. { "number": 8, "startTime": "16:50", "endTime": "17:35" },
  100. { "number": 9, "startTime": "19:00", "endTime": "19:45" },
  101. // 课间 10分
  102. { "number": 10, "startTime": "19:55", "endTime": "20:40" },
  103. // 课间 10分
  104. { "number": 11, "startTime": "20:50", "endTime": "21:35" }
  105. ];
  106. }
  107. /**
  108. * 生成全局课表配置
  109. */
  110. function getCourseConfig() {
  111. return {
  112. "defaultClassDuration": 45, // 单节课 45 分钟
  113. "defaultBreakDuration": 10 // 默认课间 10 分钟
  114. };
  115. }
  116. /**
  117. * 异步编排流程
  118. */
  119. async function runImportFlow() {
  120. try {
  121. if (typeof window.AndroidBridge !== 'undefined') {
  122. AndroidBridge.showToast("正在获取课表配置,请稍候...");
  123. } else {
  124. console.log("正在请求获取学期列表...");
  125. }
  126. // 第 1 步:请求外层页面,获取学期列表
  127. const listResponse = await fetch('/xsgrkbcx!getXsgrbkList.action', { method: 'GET' });
  128. const listHtml = await listResponse.text();
  129. const parser = new DOMParser();
  130. const listDoc = parser.parseFromString(listHtml, 'text/html');
  131. const selectElem = listDoc.getElementById('xnxqdm');
  132. let semesters = [];
  133. let semesterValues = [];
  134. let defaultIndex = 0;
  135. if (selectElem) {
  136. const options = selectElem.querySelectorAll('option');
  137. options.forEach((opt, index) => {
  138. semesters.push(opt.innerText.trim());
  139. semesterValues.push(opt.value);
  140. if (opt.hasAttribute('selected') || opt.selected) {
  141. defaultIndex = index;
  142. }
  143. });
  144. }
  145. if (semesters.length === 0) {
  146. throw new Error("无法在页面中找到学期选项(xnxqdm),请确认教务系统是否正常。");
  147. }
  148. // 第 2 步:选择学期
  149. let selectedIdx = defaultIndex;
  150. if (typeof window.AndroidBridgePromise !== 'undefined') {
  151. // APP 内原生弹窗
  152. let userChoice = await window.AndroidBridgePromise.showSingleSelection(
  153. "请选择要导入的学期",
  154. JSON.stringify(semesters),
  155. defaultIndex
  156. );
  157. if (userChoice === null) {
  158. AndroidBridge.showToast("已取消导入");
  159. return;
  160. }
  161. selectedIdx = userChoice;
  162. } else {
  163. // 电脑浏览器端原生 prompt 弹窗测试
  164. let msg = "【浏览器测试】请选择学期对应的序号:\n\n";
  165. semesters.forEach((s, idx) => {
  166. msg += `[ ${idx} ] : ${s}\n`;
  167. });
  168. let userInput = prompt(msg, defaultIndex);
  169. if (userInput === null) {
  170. console.log("已取消测试。");
  171. return;
  172. }
  173. selectedIdx = parseInt(userInput);
  174. if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= semesters.length) {
  175. alert("输入的序号不合法,将使用默认学期!");
  176. selectedIdx = defaultIndex;
  177. }
  178. }
  179. let targetXnxqdm = semesterValues[selectedIdx];
  180. if (typeof window.AndroidBridge !== 'undefined') {
  181. AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 课表数据...`);
  182. } else {
  183. console.log(`准备拉取学期 [${semesters[selectedIdx]} / 代码: ${targetXnxqdm}] 的课表数据...`);
  184. }
  185. // 第 3 步:带上学期参数,请求真正的课表数据接口
  186. const kbResponse = await fetch(`/xsgrkbcx!xsAllKbList.action?xnxqdm=${targetXnxqdm}`, { method: 'GET' });
  187. const kbHtmlText = await kbResponse.text();
  188. // 第 4 步:提取数据
  189. const courses = extractCoursesFromHtml(kbHtmlText);
  190. if (courses.length === 0) {
  191. const errMsg = "该学期暂无排课数据。";
  192. if (typeof window.AndroidBridgePromise !== 'undefined') {
  193. await window.AndroidBridgePromise.showAlert("提示", errMsg, "好的");
  194. } else {
  195. alert(errMsg);
  196. }
  197. return;
  198. }
  199. const config = getCourseConfig();
  200. const timeSlots = getPresetTimeSlots();
  201. // 浏览器测试环境,直接打印输出
  202. if (typeof window.AndroidBridgePromise === 'undefined') {
  203. console.log("【测试成功】课表配置:", config);
  204. console.log("【测试成功】作息时间:", timeSlots);
  205. console.log("【测试成功】课程数据:\n", JSON.stringify(courses, null, 2));
  206. alert(`解析成功!获取到 ${courses.length} 门课程以及专属作息。\n(请打开 F12 Console 控制台查看具体数据)`);
  207. return;
  208. }
  209. // APP 环境保存数据
  210. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  211. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  212. const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  213. if (!saveResult) {
  214. AndroidBridge.showToast("保存课程失败,请重试!");
  215. return;
  216. }
  217. AndroidBridge.showToast(`成功导入 ${courses.length} 节课程及作息时间!`);
  218. AndroidBridge.notifyTaskCompletion();
  219. } catch (error) {
  220. if (typeof window.AndroidBridge !== 'undefined') {
  221. AndroidBridge.showToast("导入发生异常: " + error.message);
  222. } else {
  223. console.error("【导入发生异常】", error);
  224. alert("导入发生异常: " + error.message);
  225. }
  226. }
  227. }
  228. // 启动导入流程
  229. runImportFlow();