gztrc_old.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. // 铜仁学院移动教务 (gztrc.edu.cn:17230/yjw) 拾光课程表适配脚本
  2. // 使用个人课表接口(courseSchedule)查询
  3. // 登录后需要从 localStorage 中读取用户信息,无需在脚本内登录
  4. const API = {
  5. semesters: "/api/baseInfo/mobile/common/querySemester",
  6. weeks: "/api/baseInfo/mobile/common/queryCurrentSemesterWeekList",
  7. times: "/api/baseInfo/mobile/common/timeNameDetail",
  8. schedule: "/api/arrange/mobile/courseSchedule/courseSchedule"
  9. };
  10. async function apiGet(path) {
  11. const res = await fetch(path, { credentials: "include" });
  12. if (!res.ok) throw new Error("请求失败: " + path);
  13. return res.json();
  14. }
  15. async function apiPost(path, data, token) {
  16. const res = await fetch(path, {
  17. method: "POST",
  18. headers: {
  19. "Content-Type": "application/json",
  20. "accessToken": token
  21. },
  22. credentials: "include",
  23. body: JSON.stringify(data)
  24. });
  25. if (!res.ok) throw new Error("请求失败: " + path);
  26. return res.json();
  27. }
  28. function parseUserFromStorage() {
  29. try {
  30. const raw = localStorage.getItem("user_info");
  31. if (!raw) return null;
  32. const data = JSON.parse(raw);
  33. if (!data || !data.accessToken || !data.userInfo || !data.userInfo.userId) return null;
  34. return {
  35. token: data.accessToken,
  36. userId: data.userInfo.userId,
  37. userType: data.userInfo.userType || "0",
  38. name: data.userInfo.name || ""
  39. };
  40. } catch (e) {
  41. return null;
  42. }
  43. }
  44. /**
  45. * 解析单条课表记录(接口按"天 x 节次"逐格返回,每格一条记录)
  46. * week 为空时使用记录自带的 weeks 字段
  47. */
  48. function parseCourseItem(c, week) {
  49. if (!c || !c.courseName) return null;
  50. const section = parseInt(c.time);
  51. if (isNaN(section) || section < 1) return null;
  52. let w = week;
  53. if (w === undefined || w === null) {
  54. w = parseInt(c.weeks);
  55. if (isNaN(w) || w < 1) return null;
  56. }
  57. // 教务dayOfWeek: 1=周日,2=周一,3=周二,4=周三,5=周四,6=周五,7=周六
  58. // 规范day: 1=周一,2=周二,3=周三,4=周四,5=周五,6=周六,7=周日
  59. const dayMap = { "1": 7, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6 };
  60. const day = dayMap[String(c.dayOfWeek)] || parseInt(c.dayOfWeek);
  61. let position = c.classroomName;
  62. if (!position || position === "," || position.trim() === "") {
  63. position = "未知地点";
  64. }
  65. return {
  66. name: c.courseName,
  67. teacher: c.teacherName || "",
  68. position: position,
  69. day: day,
  70. section: section,
  71. week: w
  72. };
  73. }
  74. function sameWeekSet(a, b) {
  75. if (a.length !== b.length) return false;
  76. for (let i = 0; i < a.length; i++) {
  77. if (a[i] !== b[i]) return false;
  78. }
  79. return true;
  80. }
  81. function pushWeek(list, w) {
  82. if (list.indexOf(w) === -1) list.push(w);
  83. }
  84. /**
  85. * 将逐格记录合并为课程:
  86. * 先按(天,课程,教师,地点,节次)聚合周次,再合并连续节次(且周次集合相同)
  87. */
  88. function mergeToCourses(records) {
  89. const groups = new Map();
  90. for (let r of records) {
  91. const key = [r.day, r.name, r.teacher, r.position].join("|");
  92. let g = groups.get(key);
  93. if (!g) {
  94. g = { day: r.day, name: r.name, teacher: r.teacher, position: r.position, secMap: new Map() };
  95. groups.set(key, g);
  96. }
  97. if (!g.secMap.has(r.section)) g.secMap.set(r.section, []);
  98. pushWeek(g.secMap.get(r.section), r.week);
  99. }
  100. const courses = [];
  101. for (let g of groups.values()) {
  102. const sections = Array.from(g.secMap.keys()).sort((a, b) => a - b);
  103. let cur = null;
  104. for (let sec of sections) {
  105. const weeks = g.secMap.get(sec).sort((a, b) => a - b);
  106. if (cur && sec === cur.endSection + 1 && sameWeekSet(cur.weeks, weeks)) {
  107. cur.endSection = sec;
  108. } else {
  109. cur = {
  110. name: g.name,
  111. teacher: g.teacher,
  112. position: g.position,
  113. day: g.day,
  114. startSection: sec,
  115. endSection: sec,
  116. weeks: weeks.slice()
  117. };
  118. courses.push(cur);
  119. }
  120. }
  121. }
  122. return courses;
  123. }
  124. async function querySchedule(user, semesterId, weeks) {
  125. const body = {
  126. academicYearSemester: semesterId,
  127. userId: user.userId,
  128. userType: user.userType,
  129. weeks: weeks
  130. };
  131. const json = await apiPost(API.schedule, body, user.token);
  132. if (json.code !== 200) {
  133. if (json.code === 53000505) {
  134. throw new Error("登录已失效,请重新登录教务系统后重试。");
  135. }
  136. throw new Error(json.message || "获取课表失败");
  137. }
  138. if (!json.data || !json.data.course) throw new Error("课表数据为空");
  139. return json.data.course;
  140. }
  141. async function fetchAllCourses(user, semesterId, totalWeeks) {
  142. // 注意:一次请求多周时,接口会把同一格子的多周数据以逗号拼接返回
  143. // (如 courseName:"课A,课B"、teacherName:"师A,师A,..."),无法可靠拆分,
  144. // 因此必须逐周查询,单周返回的数据是干净的
  145. const records = [];
  146. for (let w = 1; w <= totalWeeks; w++) {
  147. const list = await querySchedule(user, semesterId, [w]);
  148. records.push(...list.map(c => parseCourseItem(c, w)).filter(c => c !== null));
  149. }
  150. return mergeToCourses(records);
  151. }
  152. async function fetchTimeSlots() {
  153. const json = await apiGet(API.times);
  154. if (json.code !== 200 || !json.data) throw new Error("获取作息时间失败");
  155. return json.data.map(t => ({
  156. number: parseInt(t.timeCode),
  157. startTime: t.startTime,
  158. endTime: t.endTime
  159. }));
  160. }
  161. async function runImportFlow() {
  162. try {
  163. window.shiguangBridge.showToast("开始导入课表...");
  164. // 1. 读取登录信息
  165. const user = parseUserFromStorage();
  166. if (!user) {
  167. await window.shiguangBridgePromise.showAlert(
  168. "请先登录",
  169. "请在教务系统(移动教务)中完成登录,进入主页后再次点击执行导入。",
  170. "知道了"
  171. );
  172. return;
  173. }
  174. // 2. 获取学期列表
  175. window.shiguangBridge.showToast("正在获取学期列表...");
  176. const semesterJson = await apiGet(API.semesters);
  177. if (semesterJson.code !== 200) throw new Error("获取学期列表失败");
  178. const semesters = (semesterJson.data || []).filter(s => s && s.semesterId && /^\d{4}-\d{4}-\d+$/.test(s.semesterId));
  179. if (semesters.length === 0) throw new Error("未能获取学期列表");
  180. // 3. 选择学期
  181. let defaultIndex = 0;
  182. const currentIdx = semesters.findIndex(s => s.isCurrentSemester === "1");
  183. if (currentIdx !== -1) defaultIndex = currentIdx;
  184. const semesterIndex = await window.shiguangBridgePromise.showSingleSelection(
  185. "选择学期",
  186. JSON.stringify(semesters.map(s => s.semesterName)),
  187. defaultIndex
  188. );
  189. if (semesterIndex === null) {
  190. window.shiguangBridge.showToast("导入已取消。");
  191. return;
  192. }
  193. const semester = semesters[semesterIndex];
  194. // 4. 获取本学期周数
  195. let totalWeeks = 20;
  196. try {
  197. const weekJson = await apiGet(API.weeks);
  198. if (weekJson.code === 200 && weekJson.data && weekJson.data.length > 0) {
  199. totalWeeks = weekJson.data.length;
  200. }
  201. } catch (e) {
  202. // 使用默认周数
  203. }
  204. // 5. 获取课表数据
  205. window.shiguangBridge.showToast("正在获取课表数据,共 " + totalWeeks + " 周...");
  206. const courses = await fetchAllCourses(user, semester.semesterId, totalWeeks);
  207. if (courses.length === 0) throw new Error("未解析到课程数据,可能该学期暂无课表。");
  208. // 6. 保存预设时间段
  209. try {
  210. const timeSlots = await fetchTimeSlots();
  211. if (timeSlots.length > 0) {
  212. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  213. }
  214. } catch (e) {
  215. window.shiguangBridge.showToast("时间段导入失败,但课程将继续导入。");
  216. }
  217. // 7. 保存课程数据
  218. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  219. window.shiguangBridge.showToast("成功导入 " + courses.length + " 条课程记录!");
  220. window.shiguangBridge.notifyTaskCompletion();
  221. } catch (e) {
  222. console.error("[适配脚本错误] " + e.message);
  223. window.shiguangBridge.showToast("导入失败: " + e.message);
  224. }
  225. }
  226. runImportFlow();