SCUT_01.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /**
  2. * 解析正方教务系统的周次字符串
  3. */
  4. function parseZhengFangWeeks(weekStr) {
  5. if (!weekStr) return [];
  6. let weeks = [];
  7. let isOdd = weekStr.includes('单');
  8. let isEven = weekStr.includes('双');
  9. let cleanStr = weekStr.replace(/周|\(单\)|\(双\)|单|双/g, '').replace(/\s+/g, '');
  10. let parts = cleanStr.split(',');
  11. for (let part of parts) {
  12. if (part.includes('-')) {
  13. let [start, end] = part.split('-');
  14. for (let i = parseInt(start); i <= parseInt(end); i++) {
  15. if (isOdd && i % 2 === 0) continue;
  16. if (isEven && i % 2 !== 0) continue;
  17. if (!weeks.includes(i)) weeks.push(i);
  18. }
  19. } else {
  20. let w = parseInt(part);
  21. if (!isNaN(w) && !weeks.includes(w)) {
  22. if (isOdd && w % 2 === 0) continue;
  23. if (isEven && w % 2 !== 0) continue;
  24. weeks.push(w);
  25. }
  26. }
  27. }
  28. return weeks.sort((a, b) => a - b);
  29. }
  30. /**
  31. * 封装兼容 WebVPN 的原生 AJAX 请求
  32. */
  33. function requestData(url, method = 'GET', data = null) {
  34. return new Promise((resolve, reject) => {
  35. let xhr = new XMLHttpRequest();
  36. xhr.open(method, url, true);
  37. if (method === 'POST') {
  38. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
  39. xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  40. }
  41. xhr.onreadystatechange = function() {
  42. if (xhr.readyState === 4) {
  43. if (xhr.status >= 200 && xhr.status < 300) {
  44. resolve(xhr.responseText);
  45. } else {
  46. reject(new Error("网络请求失败,状态码:" + xhr.status));
  47. }
  48. }
  49. };
  50. xhr.onerror = function() {
  51. reject(new Error("网络请求发生错误,请检查网络连接。"));
  52. };
  53. xhr.send(data);
  54. });
  55. }
  56. /**
  57. * 用正则从 HTML 文本中硬核提取 select 选项
  58. */
  59. function extractOptionsByRegex(html, selectId) {
  60. let selectRegex = new RegExp(`<select[^>]*id="${selectId}"[^>]*>(.*?)<\/select>`, 'is');
  61. let selectMatch = html.match(selectRegex);
  62. if (!selectMatch) return [];
  63. let optionsHtml = selectMatch[1];
  64. let optionRegex = /<option\s+value="([^"]+)"([^>]*)>([^<]+)<\/option>/gi;
  65. let options = [];
  66. let match;
  67. while ((match = optionRegex.exec(optionsHtml)) !== null) {
  68. if (match[1].trim() !== "") {
  69. options.push({
  70. value: match[1].trim(),
  71. text: match[3].trim(),
  72. selected: match[2].includes('selected')
  73. });
  74. }
  75. }
  76. return options;
  77. }
  78. /**
  79. * 异步编排流程
  80. */
  81. async function runImportFlow() {
  82. try {
  83. if (typeof window.AndroidBridge !== 'undefined') {
  84. AndroidBridge.showToast("正在通过 WebVPN 通道读取数据...");
  85. } else {
  86. console.log("【1/4】正在获取学期选项...");
  87. }
  88. let sysPath = typeof _path !== 'undefined' ? _path : '/jwglxt';
  89. let semesters = [];
  90. let semesterValues = [];
  91. let defaultIndex = 0;
  92. // 1. 尝试直接通过正则表达式,去后台请求并提取页面中的下拉框
  93. console.log("正在后台请求页面源码...");
  94. const indexHtml = await requestData(`${sysPath}/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default`, 'GET');
  95. let yearOpts = extractOptionsByRegex(indexHtml, 'xnm');
  96. let termOpts = extractOptionsByRegex(indexHtml, 'xqm');
  97. if (yearOpts.length === 0 || termOpts.length === 0) {
  98. throw new Error("源码提取学期信息失败,可能是 WebVPN 会话过期,请重新登录教务系统!");
  99. }
  100. let count = 0;
  101. yearOpts.forEach(y => {
  102. termOpts.forEach(t => {
  103. semesters.push(`${y.text} 第${t.text}学期`);
  104. semesterValues.push({ xnm: y.value, xqm: t.value });
  105. if (y.selected && t.selected) {
  106. defaultIndex = count;
  107. }
  108. count++;
  109. });
  110. });
  111. // 2. 弹出选择框
  112. let selectedIdx = defaultIndex;
  113. if (typeof window.AndroidBridgePromise !== 'undefined') {
  114. let userChoice = await window.AndroidBridgePromise.showSingleSelection(
  115. "请选择要导入的学期",
  116. JSON.stringify(semesters),
  117. defaultIndex
  118. );
  119. if (userChoice === null) {
  120. AndroidBridge.showToast("已取消导入");
  121. return;
  122. }
  123. selectedIdx = userChoice;
  124. } else {
  125. let msg = "【浏览器测试】请选择学期对应的序号:\n\n";
  126. semesters.forEach((s, idx) => {
  127. if (Math.abs(idx - defaultIndex) <= 4) msg += `[ ${idx} ] : ${s}\n`;
  128. });
  129. msg += "\n请输入方括号中的数字:";
  130. let userInput = prompt(msg, defaultIndex);
  131. if (userInput === null) return;
  132. selectedIdx = parseInt(userInput);
  133. if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= semesters.length) {
  134. alert("输入无效,使用默认学期!");
  135. selectedIdx = defaultIndex;
  136. }
  137. }
  138. const targetData = semesterValues[selectedIdx];
  139. if (typeof window.AndroidBridge !== 'undefined') {
  140. AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 数据...`);
  141. } else {
  142. console.log(`【2/4】正在向服务器请求 [${semesters[selectedIdx]}] 的 JSON 数据...`);
  143. }
  144. // 3. 发送 Ajax 请求获取真实的课表和作息时间
  145. const postBody = `xnm=${targetData.xnm}&xqm=${targetData.xqm}&kzlx=ck&xsdm=&kclbdm=&kclxdm=`;
  146. const [kbResText, timeResText] = await Promise.all([
  147. requestData(`${sysPath}/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151`, 'POST', postBody),
  148. requestData(`${sysPath}/kbcx/xskbcx_cxRjc.html?gnmkdm=N2151`, 'POST', postBody)
  149. ]);
  150. const kbJson = JSON.parse(kbResText);
  151. const timeJson = JSON.parse(timeResText);
  152. // 4. 解析作息时间 (拦截 21:35 之后的时间)
  153. let timeSlots = [];
  154. if (Array.isArray(timeJson)) {
  155. timeJson.forEach(t => {
  156. if (t.jssj > "21:35") return;
  157. timeSlots.push({
  158. number: parseInt(t.jcmc, 10),
  159. startTime: t.qssj,
  160. endTime: t.jssj
  161. });
  162. });
  163. timeSlots.sort((a, b) => a.number - b.number);
  164. }
  165. // 5. 解析课程数据
  166. let parsedCourses = [];
  167. if (kbJson && kbJson.kbList) {
  168. kbJson.kbList.forEach(c => {
  169. let courseObj = {
  170. name: c.kcmc || "未知课程",
  171. teacher: c.xm || "未知",
  172. position: c.cdmc || "待定",
  173. day: parseInt(c.xqj),
  174. isCustomTime: false
  175. };
  176. courseObj.weeks = parseZhengFangWeeks(c.zcd);
  177. if (c.jcs) {
  178. let secParts = c.jcs.split('-');
  179. courseObj.startSection = parseInt(secParts[0]);
  180. courseObj.endSection = parseInt(secParts[secParts.length - 1] || secParts[0]);
  181. }
  182. if (courseObj.name && courseObj.weeks.length > 0 && courseObj.startSection) {
  183. parsedCourses.push(courseObj);
  184. }
  185. });
  186. }
  187. if (parsedCourses.length === 0) {
  188. const errMsg = "该学期暂无排课数据。";
  189. if (typeof window.AndroidBridgePromise !== 'undefined') {
  190. await window.AndroidBridgePromise.showAlert("提示", errMsg, "好的");
  191. } else alert(errMsg);
  192. return;
  193. }
  194. // 6. 去重
  195. let uniqueCourses = [];
  196. let courseSet = new Set();
  197. parsedCourses.forEach(course => {
  198. let uniqueKey = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.weeks.join(',')}`;
  199. if (!courseSet.has(uniqueKey)) {
  200. courseSet.add(uniqueKey);
  201. uniqueCourses.push(course);
  202. }
  203. });
  204. const config = {
  205. "defaultClassDuration": 45,
  206. "defaultBreakDuration": 5
  207. };
  208. // 7. 打印并保存
  209. if (typeof window.AndroidBridgePromise === 'undefined') {
  210. console.log("【测试成功】被 21:35 规则过滤后的作息时间表:\n", timeSlots);
  211. console.log(`【测试成功】共获取到 ${uniqueCourses.length} 门课程:\n`, JSON.stringify(uniqueCourses, null, 2));
  212. alert(`解析成功!获取到 ${uniqueCourses.length} 门课程及过滤后的作息时间。\n请打开F12控制台查看。`);
  213. return;
  214. }
  215. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  216. if (timeSlots.length > 0) {
  217. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  218. }
  219. const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(uniqueCourses));
  220. if (!saveResult) {
  221. AndroidBridge.showToast("保存失败,请重试!");
  222. return;
  223. }
  224. AndroidBridge.showToast(`成功导入 ${uniqueCourses.length} 节课程!`);
  225. AndroidBridge.notifyTaskCompletion();
  226. } catch (error) {
  227. if (typeof window.AndroidBridge !== 'undefined') {
  228. AndroidBridge.showToast("导入异常: " + error.message);
  229. } else {
  230. console.error("【导入异常】", error);
  231. alert("导入异常: " + error.message);
  232. }
  233. }
  234. }
  235. runImportFlow();