glmu.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // 文件: guilin_medical.js
  2. // 功能:从桂医教务系统获取课程表,通过桥接 API 导入到拾光课程表
  3. // ---------- 全局验证函数 ----------
  4. function validateYearInput(input) {
  5. if (/^\d{4}$/.test(input)) {
  6. return false; // 验证通过
  7. } else {
  8. return "请输入四位数字的年份(例如 2024)";
  9. }
  10. }
  11. // ---------- 工具函数 ----------
  12. function parseWeeks(weeksStr) {
  13. weeksStr = weeksStr.replace('周', '');
  14. const parts = weeksStr.split(',');
  15. const weeks = [];
  16. for (const part of parts) {
  17. if (part.includes('-')) {
  18. const [start, end] = part.split('-').map(Number);
  19. for (let i = start; i <= end; i++) weeks.push(i);
  20. } else {
  21. weeks.push(Number(part));
  22. }
  23. }
  24. return weeks;
  25. }
  26. function parseSections(jcdm) {
  27. const str = String(jcdm);
  28. const sections = [];
  29. if (str.includes('-')) {
  30. const [start, end] = str.split('-').map(Number);
  31. for (let i = start; i <= end; i++) sections.push(i);
  32. } else if (/^\d+$/.test(str)) {
  33. for (let i = 0; i < str.length; i += 2) {
  34. const sec = parseInt(str.substring(i, i + 2), 10);
  35. if (!isNaN(sec)) sections.push(sec);
  36. }
  37. } else {
  38. sections.push(parseInt(str));
  39. }
  40. return sections;
  41. }
  42. function parseRawCourses(rawData) {
  43. const courseInfos = [];
  44. for (const course of rawData) {
  45. const name = course.kcmc;
  46. const teacher = course.teaxms;
  47. const position = course.jxcdmc;
  48. const weeks = parseWeeks(course.zc);
  49. const day = parseInt(course.xq);
  50. const sections = parseSections(course.jcdm);
  51. courseInfos.push({ name, teacher, position, weeks, day, sections });
  52. }
  53. return courseInfos;
  54. }
  55. function convertToTargetCourses(middleCourses) {
  56. return middleCourses.map(c => ({
  57. name: c.name,
  58. teacher: c.teacher,
  59. position: c.position,
  60. day: c.day,
  61. startSection: c.sections[0],
  62. endSection: c.sections[c.sections.length - 1],
  63. weeks: c.weeks,
  64. isCustomTime: false
  65. }));
  66. }
  67. function getTimeSlots() {
  68. return [
  69. { number: 1, startTime: "08:30", endTime: "09:10" },
  70. { number: 2, startTime: "09:20", endTime: "10:00" },
  71. { number: 3, startTime: "10:10", endTime: "10:50" },
  72. { number: 4, startTime: "11:00", endTime: "11:40" },
  73. { number: 5, startTime: "11:50", endTime: "12:30" },
  74. { number: 6, startTime: "14:30", endTime: "15:10" },
  75. { number: 7, startTime: "15:20", endTime: "16:00" },
  76. { number: 8, startTime: "16:10", endTime: "16:50" },
  77. { number: 9, startTime: "17:00", endTime: "17:40" },
  78. { number: 10, startTime: "19:00", endTime: "19:40" },
  79. { number: 11, startTime: "19:50", endTime: "20:30" },
  80. { number: 12, startTime: "20:40", endTime: "21:20" }
  81. ];
  82. }
  83. // ---------- 网络请求 ----------
  84. async function fetchCourseData(xnxqdm) {
  85. let page = 1;
  86. const rowsPerPage = 100;
  87. let allRows = [];
  88. let total = 0;
  89. while (true) {
  90. const res = await fetch("https://ejwc.glmu.edu.cn/xsgrkbcx!getDataList.action", {
  91. headers: {
  92. "accept": "application/json, text/javascript, */*; q=0.01",
  93. "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  94. "x-requested-with": "XMLHttpRequest"
  95. },
  96. body: `xnxqdm=${xnxqdm}&page=${page}&rows=${rowsPerPage}`,
  97. method: "POST",
  98. credentials: "include"
  99. });
  100. if (!res.ok) {
  101. throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  102. }
  103. const ret = await res.json();
  104. if (!ret.rows || !Array.isArray(ret.rows)) {
  105. throw new Error("返回数据格式不正确");
  106. }
  107. total = ret.total;
  108. allRows = allRows.concat(ret.rows);
  109. if (allRows.length >= total) break;
  110. page++;
  111. if (page > 10) break;
  112. }
  113. return allRows;
  114. }
  115. // ---------- 用户交互 ----------
  116. async function promptUserToStart() {
  117. return await window.AndroidBridgePromise.showAlert(
  118. "重要提醒",
  119. "请确保您已登录桂医教务系统,且当前页面为教务系统内任意页面。\n\n点击确定继续。",
  120. "确定"
  121. );
  122. }
  123. async function getAcademicYear() {
  124. return await window.AndroidBridgePromise.showPrompt(
  125. "学年设置",
  126. "请输入本学年开始的年份(例如 2024)",
  127. "2024",
  128. "validateYearInput" // 传入验证函数名
  129. );
  130. }
  131. async function selectSemester() {
  132. const semesterOptions = ["上学期", "下学期", "短学期"];
  133. const index = await window.AndroidBridgePromise.showSingleSelection(
  134. "选择学期",
  135. JSON.stringify(semesterOptions),
  136. 0
  137. );
  138. if (index === null || index < 0) return null;
  139. return index;
  140. }
  141. // ---------- 主流程 ----------
  142. async function run() {
  143. try {
  144. // 1. 公告
  145. const confirmed = await promptUserToStart();
  146. if (!confirmed) {
  147. AndroidBridge.showToast("用户取消了导入流程。");
  148. return;
  149. }
  150. // 2. 获取学年
  151. const yearInput = await getAcademicYear();
  152. if (yearInput === null) {
  153. AndroidBridge.showToast("导入已取消。");
  154. return;
  155. }
  156. const yearNum = parseInt(yearInput);
  157. if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
  158. await window.AndroidBridgePromise.showAlert("错误", "学年输入无效,请输入2000-2100之间的数字。", "确定");
  159. return;
  160. }
  161. // 3. 获取学期
  162. const semesterIndex = await selectSemester();
  163. if (semesterIndex === null) {
  164. AndroidBridge.showToast("导入已取消。");
  165. return;
  166. }
  167. const termCode = semesterIndex === 0 ? "01" : (semesterIndex === 1 ? "02" : "03");
  168. const xnxqdm = `${yearNum}${termCode}`;
  169. // 4. 请求课表
  170. AndroidBridge.showToast("正在获取课表,请稍候...");
  171. let rawData;
  172. try {
  173. rawData = await fetchCourseData(xnxqdm);
  174. } catch (fetchErr) {
  175. await window.AndroidBridgePromise.showAlert(
  176. "网络请求失败",
  177. `请求教务系统失败:${fetchErr.message}\n\n请检查网络连接和登录状态。`,
  178. "确定"
  179. );
  180. return;
  181. }
  182. if (!rawData.length) {
  183. await window.AndroidBridgePromise.showAlert("提示", "未获取到任何课程数据。请确认已登录教务系统并选择正确的学年学期。", "确定");
  184. return;
  185. }
  186. // 5. 解析并转换
  187. const middleCourses = parseRawCourses(rawData);
  188. const targetCourses = convertToTargetCourses(middleCourses);
  189. // 6. 保存课程
  190. try {
  191. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(targetCourses));
  192. AndroidBridge.showToast(`课程数据已导入(共 ${targetCourses.length} 条)`);
  193. } catch (saveErr) {
  194. await window.AndroidBridgePromise.showAlert("保存课程失败", saveErr.message, "确定");
  195. return;
  196. }
  197. // 7. 保存时间段
  198. const timeSlots = getTimeSlots();
  199. try {
  200. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  201. AndroidBridge.showToast("时间段数据已导入");
  202. } catch (slotErr) {
  203. // 时间段保存失败不终止流程,只提示
  204. AndroidBridge.showToast(`时间段保存失败:${slotErr.message}`);
  205. }
  206. // 8. 完成通知
  207. AndroidBridge.showToast("导入完成!");
  208. AndroidBridge.notifyTaskCompletion();
  209. } catch (err) {
  210. // 捕获所有未预料的错误
  211. console.error("run error:", err);
  212. await window.AndroidBridgePromise.showAlert(
  213. "导入失败",
  214. `未知错误:${err.message || err}\n\n请联系开发者。`,
  215. "确定"
  216. );
  217. // 仍然通知完成,但可能不会生成有效文件
  218. AndroidBridge.notifyTaskCompletion();
  219. }
  220. }
  221. // 启动
  222. run();