zzu.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // 郑州大学 拾光课程表适配脚本
  2. // 适配系统:树维新一代智慧教务系统(jwxt.zzu.edu.cn)
  3. (async function () {
  4. // 郑州大学标准 12 节作息时间(第 1-4 节上午,第 5-8 节下午,第 9-12 节晚上)
  5. const presetTimeSlots = [
  6. { number: 1, startTime: "08:00", endTime: "08:45" },
  7. { number: 2, startTime: "08:55", endTime: "09:40" },
  8. { number: 3, startTime: "10:10", endTime: "10:55" },
  9. { number: 4, startTime: "11:05", endTime: "11:50" },
  10. { number: 5, startTime: "14:00", endTime: "14:45" },
  11. { number: 6, startTime: "14:55", endTime: "15:40" },
  12. { number: 7, startTime: "16:10", endTime: "16:55" },
  13. { number: 8, startTime: "17:05", endTime: "17:50" },
  14. { number: 9, startTime: "19:00", endTime: "19:45" },
  15. { number: 10, startTime: "19:55", endTime: "20:40" },
  16. { number: 11, startTime: "20:50", endTime: "21:35" },
  17. { number: 12, startTime: "21:40", endTime: "22:25" }
  18. ];
  19. function showToast(message) {
  20. if (window.shiguangBridge && window.shiguangBridge.showToast) {
  21. window.shiguangBridge.showToast(message);
  22. }
  23. }
  24. /**
  25. * 获取学期下拉列表
  26. */
  27. async function fetchSemesters() {
  28. try {
  29. const res = await fetch("/student/for-std/course-table", {
  30. headers: {
  31. "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  32. "x-requested-with": "XMLHttpRequest"
  33. },
  34. method: "GET",
  35. credentials: "include"
  36. });
  37. if (!res.ok) return null;
  38. const htmlText = await res.text();
  39. const parser = new DOMParser();
  40. const doc = parser.parseFromString(htmlText, "text/html");
  41. const select = doc.getElementById("allSemesters");
  42. if (!select) return null;
  43. const options = Array.from(select.querySelectorAll("option")).map(opt => ({
  44. label: opt.textContent.trim(),
  45. value: opt.value.trim(),
  46. selected: opt.hasAttribute("selected") || opt.selected
  47. })).filter(o => o.value && o.label);
  48. return options;
  49. } catch (e) {
  50. return null;
  51. }
  52. }
  53. /**
  54. * 获取学期开学日期与结束日期
  55. */
  56. async function fetchSemesterMetadata(semesterId) {
  57. try {
  58. const res = await fetch(`/student/ws/semester/get/${semesterId}`, {
  59. headers: {
  60. "accept": "*/*",
  61. "x-requested-with": "XMLHttpRequest"
  62. },
  63. method: "GET",
  64. credentials: "include"
  65. });
  66. if (!res.ok) return null;
  67. const data = await res.json();
  68. return {
  69. startDate: data.startDate || null,
  70. endDate: data.endDate || null
  71. };
  72. } catch (e) {
  73. return null;
  74. }
  75. }
  76. /**
  77. * 获取并解析课程表数据
  78. */
  79. async function fetchAndParseCourses(semesterId) {
  80. try {
  81. const url = `/student/for-std/course-table/semester/${semesterId}/print-data?semesterId=${semesterId}&hasExperiment=true`;
  82. const res = await fetch(url, {
  83. headers: {
  84. "accept": "*/*",
  85. "x-requested-with": "XMLHttpRequest"
  86. },
  87. method: "GET",
  88. credentials: "include"
  89. });
  90. if (!res.ok) return null;
  91. const data = await res.json();
  92. if (!data) return null;
  93. const rawActivities = (data.studentTableVms && data.studentTableVms[0] ? data.studentTableVms[0].activities : (data.studentTableVm ? data.studentTableVm.activities : (data.activities || []))) || [];
  94. if (!Array.isArray(rawActivities) || rawActivities.length === 0) return null;
  95. const parsedCourses = [];
  96. for (const act of rawActivities) {
  97. if (!act.courseName || !act.weekday || !act.startUnit || !act.endUnit || !Array.isArray(act.weekIndexes)) {
  98. continue;
  99. }
  100. const teacherName = Array.isArray(act.teachers) && act.teachers.length > 0
  101. ? act.teachers.map(t => String(t).replace(/\(\d+\)/g, "").replace(/\[\d+\]/g, "").trim()).filter(Boolean).join(",")
  102. : (typeof act.teachers === "string" ? act.teachers.replace(/\(\d+\)/g, "").trim() : "");
  103. const weeks = act.weekIndexes.map(Number).filter(w => Number.isInteger(w) && w > 0).sort((a, b) => a - b);
  104. if (weeks.length === 0) continue;
  105. const startSection = Number(act.startUnit);
  106. const endSection = Number(act.endUnit);
  107. const sections = [];
  108. for (let s = startSection; s <= endSection; s++) sections.push(s);
  109. parsedCourses.push({
  110. name: String(act.courseName).trim(),
  111. teacher: teacherName,
  112. position: String(act.room || act.building || "未知地点").trim(),
  113. day: Number(act.weekday),
  114. startSection: startSection,
  115. endSection: endSection,
  116. sections: sections,
  117. weeks: weeks
  118. });
  119. }
  120. return parsedCourses.length > 0 ? parsedCourses : null;
  121. } catch (e) {
  122. return null;
  123. }
  124. }
  125. /**
  126. * 计算学期总周数
  127. */
  128. function calculateTotalWeeks(startDate, endDate) {
  129. if (!startDate || !endDate) return 20;
  130. const start = new Date(startDate);
  131. const end = new Date(endDate);
  132. const diffMs = end.getTime() - start.getTime();
  133. if (diffMs <= 0) return 20;
  134. const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
  135. return Math.min(Math.max(Math.ceil(diffDays / 7), 16), 30);
  136. }
  137. /**
  138. * 流程主入口
  139. */
  140. async function runImportFlow() {
  141. showToast("正在拉取学期列表...");
  142. const semesters = await fetchSemesters();
  143. if (!semesters || semesters.length === 0) {
  144. showToast("获取学期列表失败,请确认已进入教务课表页面");
  145. return;
  146. }
  147. const labels = semesters.map(s => s.label);
  148. let defaultIndex = semesters.findIndex(s => s.selected);
  149. if (defaultIndex < 0) defaultIndex = 0;
  150. const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  151. "选择学期",
  152. JSON.stringify(labels),
  153. defaultIndex
  154. );
  155. if (selectedIndex === null || selectedIndex < 0 || selectedIndex >= semesters.length) {
  156. showToast("操作已取消");
  157. return;
  158. }
  159. const selectedSemester = semesters[selectedIndex];
  160. showToast("正在拉取课表数据...");
  161. const [meta, courses] = await Promise.all([
  162. fetchSemesterMetadata(selectedSemester.value),
  163. fetchAndParseCourses(selectedSemester.value)
  164. ]);
  165. if (!courses || courses.length === 0) {
  166. showToast("未查询到有效课程数据");
  167. return;
  168. }
  169. let totalWeeks = 20;
  170. if (meta && meta.startDate && meta.endDate) {
  171. totalWeeks = calculateTotalWeeks(meta.startDate, meta.endDate);
  172. } else {
  173. const allWeeks = courses.flatMap(c => c.weeks || []);
  174. if (allWeeks.length > 0) totalWeeks = Math.max(...allWeeks);
  175. }
  176. const configData = {
  177. semesterStartDate: meta && meta.startDate ? meta.startDate : "",
  178. semesterTotalWeeks: Math.max(totalWeeks, 18),
  179. firstDayOfWeek: 1,
  180. defaultClassDuration: 45,
  181. defaultBreakDuration: 10
  182. };
  183. if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveCourseConfig) {
  184. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(configData));
  185. }
  186. if (window.shiguangBridgePromise && window.shiguangBridgePromise.savePresetTimeSlots) {
  187. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  188. }
  189. if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveImportedCourses) {
  190. const saveOk = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  191. if (!saveOk) {
  192. showToast("课程保存失败");
  193. return;
  194. }
  195. }
  196. showToast(`成功导入 ${courses.length} 门课程及作息时间!`);
  197. if (window.shiguangBridge && window.shiguangBridge.notifyTaskCompletion) {
  198. window.shiguangBridge.notifyTaskCompletion();
  199. }
  200. }
  201. runImportFlow();
  202. })();