gztrc.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // 铜仁学院 (gztrc.edu.cn) 拾光课程表适配脚本
  2. // 联奕科技教务系统
  3. /**
  4. * 解析周次字符串为周次数组
  5. * 支持格式: "2-12;14-18", "2-16 双", "3-17 单", "9;11-12"
  6. */
  7. function parseWeeks(weeksStr) {
  8. const result = [];
  9. // 先按分号分割
  10. const parts = weeksStr.split(";");
  11. for (let part of parts) {
  12. part = part.trim();
  13. if (!part) continue;
  14. // 检查是否为单双周格式
  15. const doubleMatch = part.match(/^(\d+)-(\d+)\s*双$/);
  16. const singleMatch = part.match(/^(\d+)-(\d+)\s*单$/);
  17. const rangeMatch = part.match(/^(\d+)-(\d+)$/);
  18. const singleWeekMatch = part.match(/^(\d+)$/);
  19. if (doubleMatch) {
  20. const start = parseInt(doubleMatch[1]);
  21. const end = parseInt(doubleMatch[2]);
  22. for (let w = start; w <= end; w++) {
  23. if (w % 2 === 0) result.push(w);
  24. }
  25. } else if (singleMatch) {
  26. const start = parseInt(singleMatch[1]);
  27. const end = parseInt(singleMatch[2]);
  28. for (let w = start; w <= end; w++) {
  29. if (w % 2 === 1) result.push(w);
  30. }
  31. } else if (rangeMatch) {
  32. const start = parseInt(rangeMatch[1]);
  33. const end = parseInt(rangeMatch[2]);
  34. for (let w = start; w <= end; w++) {
  35. result.push(w);
  36. }
  37. } else if (singleWeekMatch) {
  38. result.push(parseInt(singleWeekMatch[1]));
  39. }
  40. }
  41. return result;
  42. }
  43. /**
  44. * 解析节次字符串(如 "1,2")为 [startSection, endSection]
  45. */
  46. function parseSection(sectionStr) {
  47. const parts = sectionStr.split(",").map(Number).sort((a, b) => a - b);
  48. return {
  49. startSection: parts[0],
  50. endSection: parts[parts.length - 1]
  51. };
  52. }
  53. /**
  54. * 从 Cookie 中解析用户信息
  55. */
  56. function parseUserFromCookie() {
  57. try {
  58. const cookies = document.cookie.split(";");
  59. for (let cookie of cookies) {
  60. cookie = cookie.trim();
  61. if (cookie.startsWith("user=")) {
  62. const userJson = decodeURIComponent(cookie.substring(5));
  63. return JSON.parse(userJson);
  64. }
  65. }
  66. return null;
  67. } catch (e) {
  68. return null;
  69. }
  70. }
  71. /**
  72. * 解析课表 API 返回的 JSON 数据
  73. */
  74. function parseCourseData(jsonData) {
  75. const courses = [];
  76. for (let item of jsonData.data) {
  77. const weekInfo = item.week;
  78. const timeInfo = item.time;
  79. const courseList = item.courseList || [];
  80. // 教务weekCode: 1=周日,2=周一,3=周二,4=周三,5=周四,6=周五,7=周六
  81. // 规范day: 1=周一,2=周二,3=周三,4=周四,5=周五,6=周六,7=周日
  82. const weekCodeMap = { "1": 7, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6 };
  83. const day = weekCodeMap[weekInfo.weekCode];
  84. for (let course of courseList) {
  85. const { startSection, endSection } = parseSection(course.time);
  86. const weeks = parseWeeks(course.weeks);
  87. courses.push({
  88. name: course.courseName,
  89. teacher: course.teacherName,
  90. position: course.classroomName || "未知地点",
  91. day: day,
  92. startSection: startSection,
  93. endSection: endSection,
  94. weeks: weeks
  95. });
  96. }
  97. }
  98. return courses;
  99. }
  100. /**
  101. * 预设时间段数据
  102. */
  103. function getTimeSlots() {
  104. return [
  105. { number: 1, startTime: "08:00", endTime: "08:45" },
  106. { number: 2, startTime: "08:55", endTime: "09:40" },
  107. { number: 3, startTime: "10:00", endTime: "10:45" },
  108. { number: 4, startTime: "10:55", endTime: "11:40" },
  109. { number: 5, startTime: "14:00", endTime: "14:45" },
  110. { number: 6, startTime: "14:55", endTime: "15:40" },
  111. { number: 7, startTime: "16:00", endTime: "16:45" },
  112. { number: 8, startTime: "16:55", endTime: "17:40" },
  113. { number: 9, startTime: "19:00", endTime: "19:45" },
  114. { number: 10, startTime: "19:55", endTime: "20:40" }
  115. ];
  116. }
  117. /**
  118. * 获取学期列表
  119. */
  120. async function fetchSemesterList() {
  121. const res = await fetch("/api/baseInfo/semester/selectXnXqListTy", {
  122. method: "GET",
  123. credentials: "include"
  124. });
  125. if (!res.ok) throw new Error("获取学期列表失败");
  126. const json = await res.json();
  127. if (json.code !== 200) throw new Error("获取学期列表失败: " + (json.message || "未知错误"));
  128. // 过滤掉无效学期(如 "-2", "-1")
  129. return json.data.filter(s => /^\d{4}-\d{4}-\d$/.test(s));
  130. }
  131. /**
  132. * 获取当前学期信息
  133. */
  134. async function fetchCurrentSemester() {
  135. const res = await fetch("/api/baseInfo/semester/selectCurrentXnXq", {
  136. method: "GET",
  137. credentials: "include"
  138. });
  139. if (!res.ok) throw new Error("获取当前学期失败");
  140. const json = await res.json();
  141. if (json.code !== 200) return null;
  142. return json.data;
  143. }
  144. /**
  145. * 获取课表数据
  146. */
  147. async function fetchCourseSchedule(semester, studentId) {
  148. const body = {
  149. semester: semester,
  150. weeks: Array.from({ length: 25 }, (_, i) => i + 1),
  151. studentId: studentId,
  152. querySource: "single",
  153. oddOrDouble: 1,
  154. startWeek: "1",
  155. stopWeek: "25"
  156. };
  157. const res = await fetch("/api/arrange/CourseScheduleAllQuery/studentCourseSchedule", {
  158. method: "POST",
  159. headers: { "Content-Type": "application/json" },
  160. credentials: "include",
  161. body: JSON.stringify(body)
  162. });
  163. if (!res.ok) throw new Error("获取课表数据失败");
  164. const json = await res.json();
  165. if (json.code !== 200) throw new Error("获取课表数据失败: " + (json.message || "未知错误"));
  166. return json;
  167. }
  168. /**
  169. * 主导入流程
  170. */
  171. async function runImportFlow() {
  172. try {
  173. AndroidBridge.showToast("开始导入课表...");
  174. // 1. 解析用户信息
  175. const userInfo = parseUserFromCookie();
  176. if (!userInfo || !userInfo.userName) {
  177. throw new Error("请先在教务系统中登录,再点击导入按钮。");
  178. }
  179. const studentId = userInfo.userName;
  180. // 2. 获取学期列表
  181. AndroidBridge.showToast("正在获取学期列表...");
  182. const semesters = await fetchSemesterList();
  183. if (!semesters || semesters.length === 0) {
  184. throw new Error("未能获取学期列表,请确认已登录教务系统。");
  185. }
  186. // 3. 获取当前学期,确定默认选中项
  187. let defaultIndex = 0;
  188. const currentSemester = await fetchCurrentSemester();
  189. if (currentSemester && currentSemester.semester) {
  190. const idx = semesters.indexOf(currentSemester.semester);
  191. if (idx !== -1) defaultIndex = idx;
  192. }
  193. // 4. 让用户选择学期
  194. const semesterIndex = await window.AndroidBridgePromise.showSingleSelection(
  195. "选择学期",
  196. JSON.stringify(semesters),
  197. defaultIndex
  198. );
  199. if (semesterIndex === null) {
  200. AndroidBridge.showToast("导入已取消。");
  201. return;
  202. }
  203. const selectedSemester = semesters[semesterIndex];
  204. // 5. 获取课表数据
  205. AndroidBridge.showToast("正在获取课表数据...");
  206. const courseData = await fetchCourseSchedule(selectedSemester, studentId);
  207. // 6. 解析课程数据
  208. const courses = parseCourseData(courseData);
  209. if (!courses || courses.length === 0) {
  210. throw new Error("未解析到课程数据,可能该学期暂无课表。");
  211. }
  212. // 7. 保存学期开始日期配置
  213. if (currentSemester && currentSemester.ksrq) {
  214. try {
  215. const config = {
  216. semesterStartDate: currentSemester.ksrq,
  217. defaultClassDuration: 50,
  218. defaultBreakDuration: 10
  219. };
  220. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  221. } catch (e) {
  222. // 忽略配置保存失败
  223. }
  224. }
  225. // 8. 保存预设时间段
  226. const timeSlots = getTimeSlots();
  227. try {
  228. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  229. } catch (e) {
  230. AndroidBridge.showToast("时间段导入失败,但课程将继续导入。");
  231. }
  232. // 9. 保存课程数据
  233. const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  234. if (saveResult) {
  235. AndroidBridge.showToast("成功导入 " + courses.length + " 条课程记录!");
  236. AndroidBridge.notifyTaskCompletion();
  237. }
  238. } catch (e) {
  239. console.error("[适配脚本错误] " + e.message);
  240. AndroidBridge.showToast("导入失败: " + e.message);
  241. }
  242. }
  243. // 启动导入流程
  244. runImportFlow();