xmcu_01.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * 基础工具函数:Base64 编码 (用于生成 URL 中的 params 参数)
  3. */
  4. function encodeParams(xn, xq) {
  5. const rawStr = `xn=${xn}&xq=${xq}`;
  6. return btoa(rawStr);
  7. }
  8. /**
  9. * 判断是否处于教务系统登录环境
  10. */
  11. async function checkLoginEnvironment() {
  12. const currentUrl = window.location.href;
  13. const targetBase = "https://jws-443.webvpn.xmcu.edu.cn/xmcsjw/";
  14. if (!currentUrl.startsWith(targetBase)) {
  15. AndroidBridge.showToast("请先登录教务系统再进行导入");
  16. return false;
  17. }
  18. return true;
  19. }
  20. /**
  21. * 数据解析函数
  22. */
  23. function parseAndMergeXmcuData(htmlText) {
  24. const parser = new DOMParser();
  25. const doc = parser.parseFromString(htmlText, 'text/html');
  26. const rawItems = [];
  27. const table = doc.getElementById('mytable');
  28. if (!table) return [];
  29. const rows = table.querySelectorAll('tr');
  30. rows.forEach((row) => {
  31. const cells = row.querySelectorAll('td.td');
  32. cells.forEach((cell, dayIndex) => {
  33. const day = dayIndex + 1;
  34. const courseDivs = cell.querySelectorAll('div[style*="padding-bottom:5px"]');
  35. courseDivs.forEach(div => {
  36. const lines = Array.from(div.childNodes)
  37. .map(n => n.textContent.trim())
  38. .filter(t => t.length > 0);
  39. if (lines.length >= 3) {
  40. const match = lines[2].match(/(.*)\[(.*)\]/);
  41. if (match) {
  42. const weeks = [];
  43. match[1].split(',').forEach(g => {
  44. if (g.includes('-')) {
  45. const [s, e] = g.split('-').map(Number);
  46. for (let i = s; i <= e; i++) weeks.push(i);
  47. } else { weeks.push(Number(g)); }
  48. });
  49. const sections = match[2].split('-').map(Number);
  50. rawItems.push({
  51. name: lines[0],
  52. teacher: lines[1],
  53. position: lines[3] || "未知地点",
  54. day: day,
  55. startSection: sections[0],
  56. endSection: sections[sections.length - 1],
  57. weeks: Array.from(new Set(weeks))
  58. });
  59. }
  60. }
  61. });
  62. });
  63. });
  64. const groupMap = new Map();
  65. rawItems.forEach(item => {
  66. const key = `${item.name}|${item.teacher}|${item.position}|${item.day}`;
  67. if (!groupMap.has(key)) groupMap.set(key, []);
  68. groupMap.get(key).push(item);
  69. });
  70. const finalCourses = [];
  71. groupMap.forEach((items, key) => {
  72. const matrix = {};
  73. items.forEach(item => {
  74. item.weeks.forEach(w => {
  75. if (!matrix[w]) matrix[w] = new Set();
  76. for (let s = item.startSection; s <= item.endSection; s++) matrix[w].add(s);
  77. });
  78. });
  79. const patternMap = new Map();
  80. Object.keys(matrix).forEach(w => {
  81. const week = parseInt(w);
  82. const sections = Array.from(matrix[week]).sort((a, b) => a - b);
  83. let start = sections[0];
  84. for (let i = 0; i < sections.length; i++) {
  85. if (i === sections.length - 1 || sections[i+1] !== sections[i] + 1) {
  86. const pKey = `${start}-${sections[i]}`;
  87. if (!patternMap.has(pKey)) patternMap.set(pKey, []);
  88. patternMap.get(pKey).push(week);
  89. if (i < sections.length - 1) start = sections[i+1];
  90. }
  91. }
  92. });
  93. const [name, teacher, position, day] = key.split('|');
  94. patternMap.forEach((weeks, pKey) => {
  95. const [sStart, sEnd] = pKey.split('-').map(Number);
  96. finalCourses.push({
  97. name, teacher, position,
  98. day: parseInt(day),
  99. startSection: sStart,
  100. endSection: sEnd,
  101. weeks: weeks.sort((a, b) => a - b)
  102. });
  103. });
  104. });
  105. return finalCourses;
  106. }
  107. /**
  108. * 学期获取函数
  109. */
  110. async function getYearAndSemester() {
  111. try {
  112. AndroidBridge.showToast("正在获取学期列表...");
  113. const response = await fetch("https://jws-443.webvpn.xmcu.edu.cn/xmcsjw/frame/droplist/getDropLists.action", {
  114. method: "POST",
  115. headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
  116. body: "comboBoxName=StMsXnxqDxDesc&paramValue=&isYXB=0&isCDDW=0&isXQ=0&isDJKSLB=0&isZY=0",
  117. credentials: "include"
  118. });
  119. const list = await response.json();
  120. const names = list.map(item => item.name);
  121. const selectedIndex = await window.AndroidBridgePromise.showSingleSelection("选择导入学期", JSON.stringify(names), 0);
  122. if (selectedIndex === null) return null;
  123. const [xn, xq] = list[selectedIndex].code.split('-');
  124. return { xn, xq };
  125. } catch (error) {
  126. AndroidBridge.showToast("获取列表失败");
  127. return null;
  128. }
  129. }
  130. /**
  131. * 课表抓取函数
  132. */
  133. async function fetchCourses(xn, xq) {
  134. try {
  135. const paramsBase64 = encodeParams(xn, xq);
  136. const url = `https://jws-443.webvpn.xmcu.edu.cn/xmcsjw/student/wsxk.xskcb10319.jsp?params=${paramsBase64}`;
  137. AndroidBridge.showToast("正在提取数据...");
  138. const response = await fetch(url, { method: "GET", credentials: "include" });
  139. const arrayBuffer = await response.arrayBuffer();
  140. const htmlText = new TextDecoder('gbk').decode(arrayBuffer);
  141. return parseAndMergeXmcuData(htmlText);
  142. } catch (error) {
  143. AndroidBridge.showToast("抓取课表失败");
  144. return null;
  145. }
  146. }
  147. /**
  148. * 时间段导入函数
  149. */
  150. async function importPresetTimeSlots() {
  151. const slots = [
  152. { "number": 1, "startTime": "08:15", "endTime": "09:00" },
  153. { "number": 2, "startTime": "09:05", "endTime": "09:50" },
  154. { "number": 3, "startTime": "10:15", "endTime": "11:00" },
  155. { "number": 4, "startTime": "11:05", "endTime": "11:50" },
  156. { "number": 5, "startTime": "14:00", "endTime": "14:45" },
  157. { "number": 6, "startTime": "14:50", "endTime": "15:35" },
  158. { "number": 7, "startTime": "16:00", "endTime": "16:45" },
  159. { "number": 8, "startTime": "16:50", "endTime": "17:35" },
  160. { "number": 9, "startTime": "18:30", "endTime": "19:15" },
  161. { "number": 10, "startTime": "19:15", "endTime": "20:00" },
  162. { "number": 11, "startTime": "20:00", "endTime": "20:45" }
  163. ];
  164. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(slots)).catch(() => {});
  165. }
  166. /**
  167. * 最终流程控制
  168. */
  169. async function runImportFlow() {
  170. // 环境检查 (非教务页面直接 Toast 退出)
  171. const isReady = await checkLoginEnvironment();
  172. if (!isReady) return;
  173. // 弹窗确认
  174. const confirmed = await window.AndroidBridgePromise.showAlert("教务导入", "建议在‘课表查询’页面进行导入以确保数据最全。", "确定");
  175. if (!confirmed) return;
  176. // 选择学期
  177. const params = await getYearAndSemester();
  178. if (!params) return;
  179. // 获取并解析数据
  180. const courses = await fetchCourses(params.xn, params.xq);
  181. if (!courses || courses.length === 0) {
  182. AndroidBridge.showToast("未找到有效课程");
  183. return;
  184. }
  185. // 存储
  186. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  187. await importPresetTimeSlots();
  188. // 完成
  189. AndroidBridge.showToast(`导入成功:共 ${courses.length} 门课程`);
  190. AndroidBridge.notifyTaskCompletion();
  191. }
  192. // 启动
  193. runImportFlow();