ysu_yjs.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // 燕山大学研究生管理系统 (yjsxt.ysu.edu.cn) 拾光课程表适配脚本
  2. // 学期与作息:GET /api/schedule/class/setting/current?yearTerm= (返回 yearTerm 列表 + lessonConfig 作息)
  3. // 课表: GET /api/schedule/table/byStudent?page=0&size=200&t=<时间戳>&yearTerm=
  4. // 字段说明:week 实为星期(1-7),lessonNumber 为节次,周次范围只在 classTimeRange 文本里(;可分隔多段)
  5. // 研究生系统没有教学日历接口,不写课表配置(开学日期非必传,避免清空用户已设值)
  6. // 把 "周X" 文本转成星期数字
  7. function parseDayFromText(seg) {
  8. const match = seg.match(/周([一二三四五六日天])/);
  9. if (!match) return 0;
  10. return { "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "日": 7, "天": 7 }[match[1]];
  11. }
  12. // 解析 classTimeRange 开头的周次段,如 "2-5" / "15" / "2,4,6"
  13. function parseWeeks(weekText) {
  14. const weeks = [];
  15. for (const part of String(weekText).split(",")) {
  16. const start = Number(part.split("-")[0]);
  17. const end = Number(part.split("-")[1] || start);
  18. for (let w = start; w <= end; w++) {
  19. if (w) weeks.push(w);
  20. }
  21. }
  22. return [...new Set(weeks)].sort((a, b) => a - b);
  23. }
  24. // 每条 data 是一节课(一个节次),按「课程名 + classTimeRange」去重后拆成课程段
  25. function parseJsonData(json) {
  26. const list = json && json.data;
  27. if (!Array.isArray(list)) return [];
  28. const seen = new Set();
  29. const courses = [];
  30. for (const item of list) {
  31. const key = item.courseName + "|" + item.classTimeRange;
  32. if (seen.has(key)) continue;
  33. seen.add(key);
  34. const teacher = (item.courseTeacher || []).map(t => t.name).join("、");
  35. const position = String(item.classroomName || "").trim();
  36. // 一个 classTimeRange 可能含多段,用「;」分隔
  37. for (const seg of String(item.classTimeRange || "").split(/[;;]/)) {
  38. const weekMatch = seg.match(/^([\d,\-]+)周/);
  39. const day = parseDayFromText(seg);
  40. const sectionMatch = seg.match(/(\d+)-(\d+)节/) || seg.match(/(\d+)节/);
  41. if (!weekMatch || !day || !sectionMatch) continue;
  42. courses.push({
  43. name: String(item.courseName || "").trim(),
  44. teacher: teacher,
  45. position: position,
  46. day: day,
  47. startSection: Number(sectionMatch[1]),
  48. endSection: Number(sectionMatch[2] || sectionMatch[1]),
  49. weeks: parseWeeks(weekMatch[1])
  50. });
  51. }
  52. }
  53. return courses;
  54. }
  55. // 无明确「当前学期」标记,按日期推断:9月~次年1月为第一学期,其余为第二学期
  56. function inferYearTerm() {
  57. const now = new Date();
  58. const year = now.getFullYear();
  59. const month = now.getMonth() + 1;
  60. if (month >= 9) return year + "-" + (year + 1) + "-1";
  61. if (month <= 1) return (year - 1) + "-" + year + "-1";
  62. return (year - 1) + "-" + year + "-2";
  63. }
  64. async function promptUserToStart() {
  65. return await window.shiguangBridgePromise.showAlert(
  66. "教务系统课表导入", "导入前请确保您已在浏览器中成功登录燕山大学研究生管理系统", "好的,开始导入");
  67. }
  68. // 接口需要 token 头鉴权,值即 satoken(Sa-Token)
  69. function getToken() {
  70. const match = document.cookie.match(/(?:^|;\s*)satoken=([^;]+)/);
  71. if (match) return decodeURIComponent(match[1]);
  72. try {
  73. return window.localStorage.getItem("satoken") || window.sessionStorage.getItem("satoken") || "";
  74. } catch (e) {
  75. return "";
  76. }
  77. }
  78. // 读取学期列表与作息配置
  79. async function fetchSetting(yearTerm) {
  80. try {
  81. const resp = await fetch("/api/schedule/class/setting/current?yearTerm=" + yearTerm,
  82. { method: "GET", credentials: "include", headers: { "token": getToken() } });
  83. if (!resp.ok) return null;
  84. const data = (await resp.json()).data;
  85. if (!data || !Array.isArray(data.yearTerm) || !Array.isArray(data.lessonConfig)) return null;
  86. return {
  87. yearTerms: data.yearTerm,
  88. timeSlots: data.lessonConfig.map(item => ({
  89. number: item.lessonNumber,
  90. startTime: String(item.lessonTime[0]).slice(11, 16),
  91. endTime: String(item.lessonTime[1]).slice(11, 16)
  92. }))
  93. };
  94. } catch (e) { return null; }
  95. }
  96. async function selectYearTerm(setting, defaultTerm) {
  97. const defaultIndex = setting.yearTerms.indexOf(defaultTerm);
  98. const index = await window.shiguangBridgePromise.showSingleSelection(
  99. "选择学期", JSON.stringify(setting.yearTerms), defaultIndex === -1 ? 0 : defaultIndex);
  100. if (index === null || index === -1) return null;
  101. return setting.yearTerms[index];
  102. }
  103. async function fetchAndParseCourses(yearTerm) {
  104. window.shiguangBridge.showToast("正在请求课表数据...");
  105. try {
  106. const resp = await fetch("/api/schedule/table/byStudent?page=0&size=200&t=" + Date.now() + "&yearTerm=" + yearTerm,
  107. { method: "GET", credentials: "include", headers: { "token": getToken() } });
  108. if (!resp.ok) throw new Error("状态码 " + resp.status);
  109. const courses = parseJsonData(await resp.json());
  110. if (!courses.length) throw new Error("未找到课程数据,请确认所选学期是否正确。");
  111. return courses;
  112. } catch (e) {
  113. window.shiguangBridge.showToast("导入失败:" + e.message);
  114. console.error("JS: Import Error", e);
  115. return null;
  116. }
  117. }
  118. async function saveCourses(courses) {
  119. try {
  120. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  121. return true;
  122. } catch (e) {
  123. window.shiguangBridge.showToast("课程保存失败: " + e.message);
  124. return false;
  125. }
  126. }
  127. async function runImportFlow() {
  128. if (!await promptUserToStart()) { window.shiguangBridge.showToast("用户取消了导入。"); return; }
  129. const defaultTerm = inferYearTerm();
  130. const setting = await fetchSetting(defaultTerm);
  131. if (!setting) { window.shiguangBridge.showToast("导入失败:未读取到课表设置,请确保已登录研究生管理系统。"); return; }
  132. const yearTerm = await selectYearTerm(setting, defaultTerm);
  133. if (yearTerm === null) { window.shiguangBridge.showToast("未选择学期,导入终止。"); return; }
  134. const courses = await fetchAndParseCourses(yearTerm);
  135. if (courses === null) return;
  136. if (!await saveCourses(courses)) return;
  137. try { await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(setting.timeSlots)); } catch (e) {}
  138. window.shiguangBridge.showToast("课程导入成功,共导入 " + courses.length + " 门课程!");
  139. window.shiguangBridge.notifyTaskCompletion();
  140. }
  141. runImportFlow();