xust_01.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // 西安科技大学(.edu.cn) 拾光课程表适配脚本
  2. // 基于正方教务系统接口适配
  3. // 非该大学开发者适配,开发者无法及时发现问题
  4. // 出现问题请提issues或者提交pr更改,这更加快速
  5. // 统一标准作息时间
  6. const TimeSlots = [
  7. { number: 1, startTime: "08:05", endTime: "08:55" },
  8. { number: 2, startTime: "09:00", endTime: "09:50" },
  9. { number: 3, startTime: "10:05", endTime: "10:55" },
  10. { number: 4, startTime: "11:00", endTime: "11:50" },
  11. { number: 5, startTime: "14:10", endTime: "15:00" },
  12. { number: 6, startTime: "15:05", endTime: "15:55" },
  13. { number: 7, startTime: "16:05", endTime: "16:55" },
  14. { number: 8, startTime: "17:00", endTime: "17:50" },
  15. { number: 9, startTime: "19:00", endTime: "19:50" },
  16. { number: 10, startTime: "19:55", endTime: "20:45" }
  17. ];
  18. /**
  19. * 解析周次字符串,处理单双周和周次范围。
  20. */
  21. function parseWeeks(weekStr) {
  22. if (!weekStr) return [];
  23. const weekSets = weekStr.split(',');
  24. let weeks = [];
  25. for (const set of weekSets) {
  26. const trimmedSet = set.trim();
  27. const rangeMatch = trimmedSet.match(/(\d+)-(\d+)周/);
  28. const singleMatch = trimmedSet.match(/^(\d+)周/); // 匹配以数字周结束的
  29. let start = 0;
  30. let end = 0;
  31. let processed = false;
  32. if (rangeMatch) { // 范围, 如 "1-5周"
  33. start = Number(rangeMatch[1]);
  34. end = Number(rangeMatch[2]);
  35. processed = true;
  36. } else if (singleMatch) { // 单个周, 如 "6周"
  37. start = end = Number(singleMatch[1]);
  38. processed = true;
  39. }
  40. if (processed) {
  41. // 确定单双周
  42. const isSingle = trimmedSet.includes('(单)');
  43. const isDouble = trimmedSet.includes('(双)');
  44. for (let w = start; w <= end; w++) {
  45. if (isSingle && w % 2 === 0) continue; // 单周跳过偶数
  46. if (isDouble && w % 2 !== 0) continue; // 双周跳过奇数
  47. weeks.push(w);
  48. }
  49. }
  50. }
  51. // 去重并排序
  52. return [...new Set(weeks)].sort((a, b) => a - b);
  53. }
  54. /**
  55. * 地点与节次差异化时间适配
  56. */
  57. function applyCustomTime(course) {
  58. if (!course.position) return course;
  59. const hasTi = course.position.includes('体');
  60. const pattern = /\d+-(9|1[0-8])-\d+/;
  61. if (!hasTi && !pattern.test(course.position)) return course;
  62. if (course.endSection < 3 || course.startSection > 4) return course;
  63. const standardStartMap = {};
  64. const standardEndMap = {};
  65. TimeSlots.forEach(slot => {
  66. standardStartMap[slot.number] = slot.startTime;
  67. standardEndMap[slot.number] = slot.endTime;
  68. });
  69. const customStartMap = { 3: "10:25", 4: "11:20" };
  70. const customEndMap = { 3: "11:15", 4: "12:10" };
  71. course.isCustomTime = true;
  72. course.customStartTime = customStartMap[course.startSection] || standardStartMap[course.startSection] || "08:05";
  73. course.customEndTime = customEndMap[course.endSection] || standardEndMap[course.endSection] || "20:45";
  74. return course;
  75. }
  76. /**
  77. * 解析 API 返回的 JSON 数据。
  78. */
  79. function parseJsonData(jsonData) {
  80. if (!jsonData || !Array.isArray(jsonData.kbList)) {
  81. console.warn("JS: JSON 数据结构错误或缺少 kbList 字段。");
  82. return [];
  83. }
  84. const rawCourseList = jsonData.kbList;
  85. const finalCourseList = [];
  86. for (const rawCourse of rawCourseList) {
  87. if (!rawCourse.kcmc || !rawCourse.xm || !rawCourse.cdmc ||
  88. !rawCourse.xqj || !rawCourse.jcs || !rawCourse.zcd) {
  89. continue;
  90. }
  91. const weeksArray = parseWeeks(rawCourse.zcd);
  92. if (weeksArray.length === 0) {
  93. continue;
  94. }
  95. const sectionParts = rawCourse.jcs.split('-');
  96. const startSection = Number(sectionParts[0]);
  97. const endSection = Number(sectionParts[sectionParts.length - 1]);
  98. const day = Number(rawCourse.xqj);
  99. if (isNaN(day) || isNaN(startSection) || isNaN(endSection) || day < 1 || day > 7 || startSection > endSection) {
  100. continue;
  101. }
  102. let courseItem = {
  103. name: rawCourse.kcmc.trim(),
  104. teacher: rawCourse.xm.trim(),
  105. position: rawCourse.cdmc.trim(),
  106. day: day,
  107. startSection: startSection,
  108. endSection: endSection,
  109. weeks: weeksArray
  110. };
  111. // 应用差异化时间逻辑
  112. courseItem = applyCustomTime(courseItem);
  113. finalCourseList.push(courseItem);
  114. }
  115. finalCourseList.sort((a, b) =>
  116. a.day - b.day ||
  117. a.startSection - b.startSection ||
  118. a.name.localeCompare(b.name)
  119. );
  120. return finalCourseList;
  121. }
  122. function validateYearInput(input) {
  123. console.log("JS: validateYearInput 被调用,输入: " + input);
  124. if (/^[0-9]{4}$/.test(input)) {
  125. console.log("JS: validateYearInput 验证通过。");
  126. return false;
  127. } else {
  128. console.log("JS: validateYearInput 验证失败。");
  129. return "请输入四位数字的学年!";
  130. }
  131. }
  132. async function promptUserToStart() {
  133. return await window.shiguangBridgePromise.showAlert(
  134. "教务系统课表导入",
  135. "导入前请确保您已在浏览器中成功登录教务系统",
  136. "好的,开始导入"
  137. );
  138. }
  139. async function getAcademicYear() {
  140. const currentYear = new Date().getFullYear().toString();
  141. return await window.shiguangBridgePromise.showPrompt(
  142. "选择学年",
  143. "请输入要导入课程的起始学年(例如 2025-2026 应输入2025):",
  144. currentYear,
  145. "validateYearInput"
  146. );
  147. }
  148. async function selectSemester() {
  149. const semesters = ["第一学期", "第二学期"];
  150. return await window.shiguangBridgePromise.showSingleSelection(
  151. "选择学期",
  152. JSON.stringify(semesters),
  153. 0
  154. );
  155. }
  156. /**
  157. * 将选择索引转换为 API 所需的学期码。
  158. */
  159. function getSemesterCode(semesterIndex) {
  160. // semesterIndex 3 (第一学期), 12 (第二学期)
  161. return semesterIndex === 0 ? "3" : "12";
  162. }
  163. /**
  164. * 请求和解析课程数据
  165. */
  166. async function fetchAndParseCourses(academicYear, semesterIndex) {
  167. const semesterCode = getSemesterCode(semesterIndex);
  168. const requestBody = `xnm=${academicYear}&xqm=${semesterCode}&kzlx=ck&xsdm=&kclbdm=`;
  169. const targetUrls = [
  170. "http://59.74.174.150/jwglxt/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151"
  171. ];
  172. for (const url of targetUrls) {
  173. try {
  174. const response = await fetch(url, {
  175. method: "POST",
  176. headers: {
  177. "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
  178. },
  179. body: requestBody,
  180. credentials: "include"
  181. });
  182. if (response.ok) {
  183. const jsonText = await response.text();
  184. const jsonData = JSON.parse(jsonText);
  185. if (jsonData && jsonData.kbList) {
  186. const parsedCourses = parseJsonData(jsonData);
  187. if (parsedCourses.length > 0) {
  188. return {
  189. courses: parsedCourses,
  190. config: {
  191. semesterStartDate: null,
  192. semesterTotalWeeks: 20
  193. }
  194. };
  195. }
  196. }
  197. }
  198. } catch (e) {
  199. console.error(`Entry failed: ${url}`);
  200. }
  201. }
  202. window.shiguangBridge.showToast("未能获取课表数据,请检查网络环境或登录状态。");
  203. return null;
  204. }
  205. async function saveCourses(parsedCourses) {
  206. window.shiguangBridge.showToast(`正在保存 ${parsedCourses.length} 门课程...`);
  207. try {
  208. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses, null, 2));
  209. return true;
  210. } catch (error) {
  211. window.shiguangBridge.showToast(`课程保存失败: ${error.message}`);
  212. return false;
  213. }
  214. }
  215. async function importPresetTimeSlots(timeSlots) {
  216. if (timeSlots.length > 0) {
  217. window.shiguangBridge.showToast(`正在导入 ${timeSlots.length} 个预设时间段...`);
  218. try {
  219. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  220. window.shiguangBridge.showToast("预设时间段导入成功!");
  221. } catch (error) {
  222. window.shiguangBridge.showToast("导入时间段失败: " + error.message);
  223. }
  224. } else {
  225. window.shiguangBridge.showToast("警告:时间段为空,未导入时间段信息。");
  226. }
  227. }
  228. async function runImportFlow() {
  229. const alertConfirmed = await promptUserToStart();
  230. if (!alertConfirmed) {
  231. window.shiguangBridge.showToast("用户取消了导入。");
  232. return;
  233. }
  234. const academicYear = await getAcademicYear();
  235. if (academicYear === null) {
  236. window.shiguangBridge.showToast("导入已取消。");
  237. return;
  238. }
  239. const semesterIndex = await selectSemester();
  240. if (semesterIndex === null || semesterIndex === -1) {
  241. window.shiguangBridge.showToast("导入已取消。");
  242. return;
  243. }
  244. const result = await fetchAndParseCourses(academicYear, semesterIndex);
  245. if (result === null) {
  246. return;
  247. }
  248. const { courses, config } = result;
  249. const saveResult = await saveCourses(courses);
  250. if (!saveResult) {
  251. return;
  252. }
  253. try {
  254. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
  255. window.shiguangBridge.showToast(`课表配置更新成功!总周数:${config.semesterTotalWeeks}周。`);
  256. } catch (error) {
  257. window.shiguangBridge.showToast(`课表配置保存失败: ${error.message}`);
  258. }
  259. await importPresetTimeSlots(TimeSlots);
  260. window.shiguangBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
  261. window.shiguangBridge.notifyTaskCompletion();
  262. }
  263. runImportFlow();