zcmu.js 10 KB

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