hbmu.js 8.4 KB

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