yzu.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // 扬州大学(yzu.edu.cn) 拾光课程表适配脚本
  2. // 基于URP教务系统接口适配
  3. const PAGE_URL = window.location.href;
  4. const VPN_BASE = (PAGE_URL.match(/^(https?:\/\/[^/]+\/http[^/]*\/[^/]+)/) || [])[1];
  5. if (!VPN_BASE) {
  6. window.shiguangBridge.showToast("请通过扬州大学 WebVPN 进入教务系统后使用");
  7. throw new Error("无法定位 WebVPN 基址");
  8. }
  9. const API_PATH = "/student/courseSelect/thisSemesterCurriculum/ajaxStudentSchedule/callback";
  10. function parseWeeks(s) {
  11. const w = [];
  12. if (!s) return w;
  13. for (let i = 0; i < s.length; i++) if (s[i] === '1') w.push(i + 1);
  14. return w;
  15. }
  16. function cleanTeacher(t) {
  17. return (t || "").replace(/\*/g, "").trim();
  18. }
  19. function parseCourses(data) {
  20. const courses = [];
  21. const seen = new Set();
  22. const list = (data && data.xkxx) || [];
  23. for (let i = 0; i < list.length; i++) {
  24. const map = list[i];
  25. if (!map) continue;
  26. const keys = Object.keys(map);
  27. for (let j = 0; j < keys.length; j++) {
  28. const c = map[keys[j]];
  29. if (!c || !c.courseName || !c.timeAndPlaceList) continue;
  30. const teacher = cleanTeacher(c.attendClassTeacher);
  31. const tpl = c.timeAndPlaceList;
  32. for (let k = 0; k < tpl.length; k++) {
  33. const tp = tpl[k];
  34. const start = tp.classSessions;
  35. const weeks = parseWeeks(tp.classWeek);
  36. if (!start || !tp.classDay || weeks.length === 0) continue;
  37. const key = c.courseName + '|' + teacher + '|' + tp.classDay + '|' + start + '|' + (tp.classSessions + tp.continuingSession - 1) + '|' + weeks.join(',');
  38. if (seen.has(key)) continue;
  39. seen.add(key);
  40. courses.push({
  41. name: c.courseName,
  42. teacher: teacher,
  43. position: ((tp.campusName || "") + (tp.classroomName || "")).trim() || "待定",
  44. day: tp.classDay,
  45. startSection: start,
  46. endSection: tp.classSessions + tp.continuingSession - 1,
  47. weeks: weeks,
  48. isCustomTime: false
  49. });
  50. }
  51. }
  52. }
  53. return courses;
  54. }
  55. function parseTimeSlots(data) {
  56. const slots = [];
  57. const list = (data && data.jcsjbs) || [];
  58. for (let i = 0; i < list.length; i++) {
  59. const it = list[i];
  60. const n = parseInt(it.jc, 10);
  61. const st = it.kssj, et = it.jssj;
  62. if (isNaN(n) || !st || st.length !== 4 || !et || et.length !== 4) continue;
  63. slots.push({
  64. number: n,
  65. startTime: st.substring(0, 2) + ":" + st.substring(2),
  66. endTime: et.substring(0, 2) + ":" + et.substring(2)
  67. });
  68. }
  69. return slots.sort((a, b) => a.number - b.number);
  70. }
  71. const FALLBACK_TIME_SLOTS = [
  72. { "number": 1, "startTime": "08:00", "endTime": "08:45" },
  73. { "number": 2, "startTime": "08:55", "endTime": "09:40" },
  74. { "number": 3, "startTime": "09:55", "endTime": "10:40" },
  75. { "number": 4, "startTime": "10:50", "endTime": "11:35" },
  76. { "number": 5, "startTime": "11:45", "endTime": "12:30" },
  77. { "number": 6, "startTime": "14:30", "endTime": "15:15" },
  78. { "number": 7, "startTime": "15:25", "endTime": "16:10" },
  79. { "number": 8, "startTime": "16:20", "endTime": "17:05" },
  80. { "number": 9, "startTime": "17:15", "endTime": "18:00" },
  81. { "number": 10, "startTime": "18:10", "endTime": "18:55" },
  82. { "number": 11, "startTime": "19:30", "endTime": "20:15" },
  83. { "number": 12, "startTime": "20:25", "endTime": "21:10" }
  84. ];
  85. async function runImportFlow() {
  86. try {
  87. const confirmed = await window.shiguangBridgePromise.showAlert(
  88. "扬州大学课表导入",
  89. "请确认:\n1. 已登录扬州大学 WebVPN(webvpn.yzu.edu.cn)\n2. 已进入教务「我的课表」页面(能看到学期下拉框)\n未在课表页会导致获取失败。",
  90. "好的,开始导入"
  91. );
  92. if (!confirmed) return;
  93. const select = document.getElementById("planCode");
  94. if (!select || select.options.length === 0) {
  95. window.shiguangBridge.showToast("未找到学期选择框,请先进入课表页面");
  96. return;
  97. }
  98. const texts = [];
  99. const values = [];
  100. for (let i = 0; i < select.options.length; i++) {
  101. texts.push(select.options[i].text);
  102. values.push(select.options[i].value);
  103. }
  104. const defaultIndex = select.selectedIndex >= 0 ? select.selectedIndex : 0;
  105. const idx = await window.shiguangBridgePromise.showSingleSelection(
  106. "选择学期",
  107. JSON.stringify(texts),
  108. defaultIndex
  109. );
  110. if (idx === null || idx < 0) {
  111. window.shiguangBridge.showToast("导入已取消");
  112. return;
  113. }
  114. window.shiguangBridge.showToast("正在获取教务数据...");
  115. const res = await fetch(VPN_BASE + API_PATH, {
  116. "headers": {
  117. "Accept": "application/json, text/javascript, */*; q=0.01",
  118. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  119. "X-Requested-With": "XMLHttpRequest"
  120. },
  121. "body": "planCode=" + values[idx],
  122. "method": "POST",
  123. "credentials": "include"
  124. });
  125. if (!res.ok) throw new Error("网络请求失败,状态码: " + res.status);
  126. const data = await res.json();
  127. const courses = parseCourses(data);
  128. if (courses.length === 0) {
  129. window.shiguangBridge.showToast("未解析到课程数据,请确认所选学期有课");
  130. return;
  131. }
  132. const timeSlots = parseTimeSlots(data);
  133. const ok = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  134. if (!ok) {
  135. window.shiguangBridge.showToast("课程保存失败");
  136. return;
  137. }
  138. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots.length ? timeSlots : FALLBACK_TIME_SLOTS));
  139. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({ semesterTotalWeeks: 20 }));
  140. window.shiguangBridge.showToast("成功导入 " + courses.length + " 个课程时段");
  141. window.shiguangBridge.notifyTaskCompletion();
  142. } catch (e) {
  143. console.error("导入异常:", e);
  144. window.shiguangBridge.showToast("导入失败: " + e.message);
  145. }
  146. }
  147. runImportFlow();