glmu.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // 文件: school.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. // 🔥 修复核心:兼容数字、数组和字符串
  14. if (typeof weeksStr === 'number') {
  15. return [weeksStr];
  16. }
  17. if (Array.isArray(weeksStr)) {
  18. return weeksStr.map(Number);
  19. }
  20. // 兼容字符串情况
  21. let str = String(weeksStr).replace('周', '');
  22. const parts = str.split(',');
  23. const weeks = [];
  24. for (const part of parts) {
  25. if (part.includes('-')) {
  26. const [start, end] = part.split('-').map(Number);
  27. for (let i = start; i <= end; i++) weeks.push(i);
  28. } else {
  29. weeks.push(Number(part));
  30. }
  31. }
  32. return weeks;
  33. }
  34. // 兼容旧字段 jcdm 的解析(保留作为备用)
  35. function parseSections(jcdm) {
  36. const str = String(jcdm);
  37. const sections = [];
  38. if (str.includes('-')) {
  39. const [start, end] = str.split('-').map(Number);
  40. for (let i = start; i <= end; i++) sections.push(i);
  41. } else if (/^\d+$/.test(str)) {
  42. for (let i = 0; i < str.length; i += 2) {
  43. const sec = parseInt(str.substring(i, i + 2), 10);
  44. if (!isNaN(sec)) sections.push(sec);
  45. }
  46. } else {
  47. sections.push(parseInt(str));
  48. }
  49. return sections;
  50. }
  51. function parseRawCourses(rawData) {
  52. const courseInfos = [];
  53. for (const course of rawData) {
  54. const name = course.kcmc;
  55. const teacher = course.teaxms;
  56. const position = course.jxcdmc;
  57. const weeks = parseWeeks(course.zc); // 这里调用已经修复
  58. const day = parseInt(course.xq);
  59. // 使用 ps 和 pe 字段来生成 sections 数组
  60. let sections = [];
  61. if (course.ps !== undefined && course.pe !== undefined && course.ps !== '' && course.pe !== '') {
  62. const start = parseInt(course.ps, 10);
  63. const end = parseInt(course.pe, 10);
  64. if (!isNaN(start) && !isNaN(end)) {
  65. for (let i = start; i <= end; i++) {
  66. sections.push(i);
  67. }
  68. }
  69. } else if (course.jcdm !== undefined) {
  70. sections = parseSections(course.jcdm);
  71. }
  72. if (sections.length === 0) {
  73. console.warn('跳过无节次信息的课程:', course.kcmc);
  74. continue;
  75. }
  76. courseInfos.push({ name, teacher, position, weeks, day, sections });
  77. }
  78. return courseInfos;
  79. }
  80. function convertToTargetCourses(middleCourses) {
  81. return middleCourses.map(c => ({
  82. name: c.name,
  83. teacher: c.teacher,
  84. position: c.position,
  85. day: c.day,
  86. startSection: c.sections[0],
  87. endSection: c.sections[c.sections.length - 1],
  88. weeks: c.weeks,
  89. isCustomTime: false
  90. }));
  91. }
  92. function getTimeSlots() {
  93. return [
  94. { number: 1, startTime: "08:30", endTime: "09:10" },
  95. { number: 2, startTime: "09:20", endTime: "10:00" },
  96. { number: 3, startTime: "10:10", endTime: "10:50" },
  97. { number: 4, startTime: "11:00", endTime: "11:40" },
  98. { number: 5, startTime: "11:50", endTime: "12:30" },
  99. { number: 6, startTime: "14:30", endTime: "15:10" },
  100. { number: 7, startTime: "15:20", endTime: "16:00" },
  101. { number: 8, startTime: "16:10", endTime: "16:50" },
  102. { number: 9, startTime: "17:00", endTime: "17:40" },
  103. { number: 10, startTime: "19:00", endTime: "19:40" },
  104. { number: 11, startTime: "19:50", endTime: "20:30" },
  105. { number: 12, startTime: "20:40", endTime: "21:20" }
  106. ];
  107. }
  108. // ---------- 网络请求 ----------
  109. async function fetchCourseData(xnxqdm) {
  110. const url = "https://ejwc.glmu.edu.cn/new/student/xsgrkb/getCalendarWeekDatas";
  111. // 🔥 核心修改:并发请求第 1 周到第 20 周
  112. const promises = [];
  113. for (let zc = 1; zc <= 20; zc++) {
  114. const bodyParams = `xnxqdm=${xnxqdm}&zc=${zc}`;
  115. promises.push(
  116. fetch(url, {
  117. headers: {
  118. "accept": "application/json, text/javascript, */*; q=0.01",
  119. "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  120. "x-requested-with": "XMLHttpRequest"
  121. },
  122. body: bodyParams,
  123. method: "POST",
  124. credentials: "include"
  125. })
  126. .then(res => res.json())
  127. .then(ret => {
  128. // 深度兼容新版数据结构
  129. let courseData = null;
  130. if (Array.isArray(ret)) {
  131. courseData = ret;
  132. } else if (ret.rows && Array.isArray(ret.rows)) {
  133. courseData = ret.rows;
  134. } else if (ret.data) {
  135. if (Array.isArray(ret.data)) {
  136. courseData = ret.data;
  137. } else if (ret.data.rows && Array.isArray(ret.data.rows)) {
  138. courseData = ret.data.rows;
  139. } else if (ret.data.list && Array.isArray(ret.data.list)) {
  140. courseData = ret.data.list;
  141. }
  142. } else if (ret.result && Array.isArray(ret.result)) {
  143. courseData = ret.result;
  144. } else if (ret.list && Array.isArray(ret.list)) {
  145. courseData = ret.list;
  146. }
  147. if (courseData && courseData.length > 0) {
  148. // 安全补全:如果教务系统返回的数据里没有 zc 字段,补上当前请求的周次
  149. courseData.forEach(course => {
  150. if (!course.zc) {
  151. course.zc = String(zc);
  152. }
  153. });
  154. return courseData;
  155. }
  156. return [];
  157. })
  158. .catch(err => {
  159. console.error(`请求第 ${zc} 周数据出错:`, err);
  160. return []; // 某一周失败不影响整体
  161. })
  162. );
  163. }
  164. // 等待所有 20 个请求并发完成
  165. const results = await Promise.all(promises);
  166. // 合并结果
  167. let allRows = [];
  168. results.forEach(rows => {
  169. allRows = allRows.concat(rows);
  170. });
  171. return allRows;
  172. }
  173. // ---------- 用户交互 ----------
  174. async function promptUserToStart() {
  175. return await window.shiguangBridgePromise.showAlert(
  176. "重要提醒",
  177. "请确保您已登录桂医教务系统,且当前页面为教务系统内任意页面。\n\n点击确定继续。",
  178. "确定"
  179. );
  180. }
  181. async function getAcademicYear() {
  182. return await window.shiguangBridgePromise.showPrompt(
  183. "学年设置",
  184. "请输入本学年开始的年份\n(例如 2024,代表 2024-2025 学年)",
  185. "2024",
  186. "validateYearInput"
  187. );
  188. }
  189. async function selectSemester() {
  190. const semesterOptions = ["上学期", "下学期", "短学期"];
  191. const index = await window.shiguangBridgePromise.showSingleSelection(
  192. "选择学期",
  193. JSON.stringify(semesterOptions),
  194. 0
  195. );
  196. if (index === null || index < 0) return null;
  197. return index;
  198. }
  199. // ---------- 主流程 ----------
  200. async function run() {
  201. try {
  202. const confirmed = await promptUserToStart();
  203. if (!confirmed) {
  204. window.shiguangBridge.showToast("用户取消了导入流程。");
  205. return;
  206. }
  207. const yearInput = await getAcademicYear();
  208. if (yearInput === null) {
  209. window.shiguangBridge.showToast("导入已取消。");
  210. return;
  211. }
  212. const yearNum = parseInt(yearInput);
  213. if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
  214. await window.shiguangBridgePromise.showAlert("错误", "学年输入无效,请输入2000-2100之间的数字。", "确定");
  215. return;
  216. }
  217. const semesterIndex = await selectSemester();
  218. if (semesterIndex === null) {
  219. window.shiguangBridge.showToast("导入已取消。");
  220. return;
  221. }
  222. const termCode = semesterIndex === 0 ? "01" : (semesterIndex === 1 ? "02" : "03");
  223. const xnxqdm = `${yearNum}${termCode}`;
  224. window.shiguangBridge.showToast("正在获取课表,请稍候...");
  225. let rawData;
  226. try {
  227. rawData = await fetchCourseData(xnxqdm);
  228. } catch (fetchErr) {
  229. await window.shiguangBridgePromise.showAlert(
  230. "网络请求失败",
  231. `请求教务系统失败:${fetchErr.message}\n\n请检查网络连接和登录状态。`,
  232. "确定"
  233. );
  234. return;
  235. }
  236. if (!rawData.length) {
  237. await window.shiguangBridgePromise.showAlert("提示", "未获取到任何课程数据。请确认已登录教务系统并选择正确的学年学期。", "确定");
  238. return;
  239. }
  240. const middleCourses = parseRawCourses(rawData);
  241. const targetCourses = convertToTargetCourses(middleCourses);
  242. try {
  243. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(targetCourses));
  244. window.shiguangBridge.showToast(`课程数据已导入(共 ${targetCourses.length} 条)`);
  245. } catch (saveErr) {
  246. await window.shiguangBridgePromise.showAlert("保存课程失败", saveErr.message, "确定");
  247. return;
  248. }
  249. const timeSlots = getTimeSlots();
  250. try {
  251. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  252. window.shiguangBridge.showToast("时间段数据已导入");
  253. } catch (slotErr) {
  254. window.shiguangBridge.showToast(`时间段保存失败:${slotErr.message}`);
  255. }
  256. window.shiguangBridge.showToast("导入完成!");
  257. window.shiguangBridge.notifyTaskCompletion();
  258. } catch (err) {
  259. console.error("run error:", err);
  260. await window.shiguangBridgePromise.showAlert(
  261. "导入失败",
  262. `未知错误:${err.message || err}\n\n请联系开发者。`,
  263. "确定"
  264. );
  265. window.shiguangBridge.notifyTaskCompletion();
  266. }
  267. }
  268. // 启动
  269. run();