shxy.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // 绥化学院(shxy.edu.cn) 拾光课程表适配脚本
  2. // 基于乘方教务系统接口适配
  3. // 出现问题请提issues或者提交pr更改
  4. /**
  5. * 解析周次字符串,例如 "1,2,3,4,5" -> [1, 2, 3, 4, 5]
  6. */
  7. function parseWeeks(weekStr) {
  8. if (!weekStr) return [];
  9. const weeks = weekStr.split(',')
  10. .map(w => Number(w.trim()))
  11. .filter(w => !isNaN(w) && w > 0);
  12. return [...new Set(weeks)].sort((a, b) => a - b);
  13. }
  14. /**
  15. * 将教务系统返回的 JSON 转换为拾光标准的 CourseJsonModel 数组
  16. */
  17. function parseJsonData(jsonData) {
  18. console.log("JS: 开始解析课程 JSON...");
  19. if (!jsonData || jsonData.code !== 0 || !Array.isArray(jsonData.data)) {
  20. return [];
  21. }
  22. let allProcessedCourses = [];
  23. jsonData.data.forEach(raw => {
  24. // 基础字段校验
  25. if (!(raw.kcmc && raw.xq && raw.ps && raw.pe)) return;
  26. // 识别并解析 jxcdmc2
  27. const locationMap = new Map();
  28. if (raw.jxcdmc2) {
  29. const parts = raw.jxcdmc2.split(',');
  30. parts.forEach(part => {
  31. const lastDashIndex = part.lastIndexOf('-');
  32. if (lastDashIndex !== -1) {
  33. const room = part.substring(0, lastDashIndex).trim();
  34. const week = Number(part.substring(lastDashIndex + 1));
  35. if (!isNaN(week)) {
  36. if (!locationMap.has(room)) locationMap.set(room, []);
  37. locationMap.get(room).push(week);
  38. }
  39. }
  40. });
  41. }
  42. if (locationMap.size > 0) {
  43. // 如果解析成功,则根据地点拆分为多个课程对象
  44. locationMap.forEach((weeks, room) => {
  45. allProcessedCourses.push({
  46. name: raw.kcmc.trim(),
  47. teacher: (raw.teaxms || "未知教师").trim(),
  48. position: room || "未知地点",
  49. day: Number(raw.xq),
  50. startSection: Number(raw.ps),
  51. endSection: Number(raw.pe),
  52. weeks: weeks.sort((a, b) => a - b)
  53. });
  54. });
  55. } else if (raw.zc) {
  56. // 兜底方案:如果 jxcdmc2 无效,使用原有的 jxcdmc 和 zc 逻辑
  57. allProcessedCourses.push({
  58. name: raw.kcmc.trim(),
  59. teacher: (raw.teaxms || "未知教师").trim(),
  60. position: (raw.jxcdmc || "未知地点").trim(),
  61. day: Number(raw.xq),
  62. startSection: Number(raw.ps),
  63. endSection: Number(raw.pe),
  64. weeks: parseWeeks(raw.zc)
  65. });
  66. }
  67. });
  68. return allProcessedCourses.filter(course => {
  69. return course.weeks.length > 0 && course.startSection <= course.endSection;
  70. });
  71. }
  72. /**
  73. * 校验函数:验证用户输入的学年格式
  74. */
  75. function validateYearInput(input) {
  76. return /^[0-9]{4}$/.test(input) ? false : "请输入四位数字的起始学年(如 2025)";
  77. }
  78. /**
  79. * 步骤 A: 显示引导公告
  80. */
  81. async function promptUserToStart() {
  82. return await window.shiguangBridgePromise.showAlert(
  83. "教务导入说明",
  84. "1. 请确保已在浏览器中成功登录教务系统\n2. 导入过程中请勿关闭页面",
  85. "好的,开始导入"
  86. );
  87. }
  88. /**
  89. * 步骤 B: 获取用户输入的学年
  90. */
  91. async function getAcademicYear() {
  92. const currentYear = new Date().getFullYear().toString();
  93. return await window.shiguangBridgePromise.showPrompt(
  94. "选择学年",
  95. "请输入要导入的起始学年(例如 2025-2026 应输入2025):",
  96. currentYear,
  97. "validateYearInput"
  98. );
  99. }
  100. /**
  101. * 步骤 C: 选择学期
  102. */
  103. async function selectSemester() {
  104. const semesters = ["第一学期 (秋季)", "第二学期 (春季)"];
  105. return await window.shiguangBridgePromise.showSingleSelection(
  106. "选择学期",
  107. JSON.stringify(semesters),
  108. 0
  109. );
  110. }
  111. /**
  112. * 发起网络请求并获取数据
  113. */
  114. async function fetchCourses(academicYear, semesterIndex) {
  115. window.shiguangBridge.showToast("正在请求教务数据...");
  116. const semesterCode = semesterIndex === 0 ? "01" : "02";
  117. const body = `xnxqdm=${academicYear}${semesterCode}`;
  118. const url = "http://jwgl.shxy.edu.cn/new/student/xsgrkb/getCalendarWeekDatas";
  119. try {
  120. const response = await fetch(url, {
  121. method: "POST",
  122. headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
  123. body: body,
  124. credentials: "include"
  125. });
  126. if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
  127. const jsonData = await response.json();
  128. return parseJsonData(jsonData);
  129. } catch (e) {
  130. console.error("Fetch Error:", e);
  131. window.shiguangBridge.showToast("网络请求失败,请检查登录状态");
  132. return null;
  133. }
  134. }
  135. const CampusTimeSlots = {
  136. "老校区(厚德楼)": [
  137. { number: 1, startTime: "08:10", endTime: "08:55" },
  138. { number: 2, startTime: "09:00", endTime: "09:45" },
  139. { number: 3, startTime: "10:15", endTime: "11:00" },
  140. { number: 4, startTime: "11:05", endTime: "11:50" },
  141. { number: 5, startTime: "13:45", endTime: "14:30" },
  142. { number: 6, startTime: "14:35", endTime: "15:20" },
  143. { number: 7, startTime: "15:40", endTime: "16:25" },
  144. { number: 8, startTime: "16:30", endTime: "17:15" },
  145. { number: 9, startTime: "18:30", endTime: "19:15" },
  146. { number: 10, startTime: "19:20", endTime: "20:05" }
  147. ],
  148. "新校区(崇德楼)": [
  149. { number: 1, startTime: "08:10", endTime: "08:55" },
  150. { number: 2, startTime: "09:00", endTime: "09:45" },
  151. { number: 3, startTime: "10:30", endTime: "11:15" },
  152. { number: 4, startTime: "11:20", endTime: "12:05" },
  153. { number: 5, startTime: "13:45", endTime: "14:30" },
  154. { number: 6, startTime: "14:35", endTime: "15:20" },
  155. { number: 7, startTime: "15:55", endTime: "16:40" },
  156. { number: 8, startTime: "16:45", endTime: "17:30" },
  157. { number: 9, startTime: "18:30", endTime: "19:15" },
  158. { number: 10, startTime: "19:20", endTime: "20:05" }
  159. ]
  160. };
  161. async function selectCampus() {
  162. const campuses = Object.keys(CampusTimeSlots);
  163. const idx = await window.shiguangBridgePromise.showSingleSelection(
  164. "选择校区", JSON.stringify(campuses), 0
  165. );
  166. if (idx === null || idx === -1) return null;
  167. return campuses[idx];
  168. }
  169. // 主流程编排
  170. async function runImportFlow() {
  171. // 1. 前置检查
  172. const isReady = await promptUserToStart();
  173. if (!isReady) return;
  174. // 2. 选择校区
  175. const campus = await selectCampus();
  176. if (!campus) {
  177. window.shiguangBridge.showToast("未选择校区,导入终止。");
  178. return;
  179. }
  180. // 3. 获取参数(学年、学期)
  181. const year = await getAcademicYear();
  182. if (!year) {
  183. window.shiguangBridge.showToast("导入已取消");
  184. return;
  185. }
  186. const semesterIdx = await selectSemester();
  187. if (semesterIdx === null) {
  188. window.shiguangBridge.showToast("导入已取消");
  189. return;
  190. }
  191. // 4. 执行获取与解析
  192. const courses = await fetchCourses(year, semesterIdx);
  193. if (!courses || courses.length === 0) {
  194. if (courses && courses.length === 0) window.shiguangBridge.showToast("该学期暂无课程数据");
  195. return;
  196. }
  197. // 5. 数据保存
  198. try {
  199. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  200. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(CampusTimeSlots[campus]));
  201. window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!(${campus})`);
  202. window.shiguangBridge.notifyTaskCompletion();
  203. console.log("JS: 流程成功完成");
  204. } catch (e) {
  205. window.shiguangBridge.showToast("保存失败: " + e.message);
  206. }
  207. }
  208. // 启动入口
  209. runImportFlow();