dlu.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. // 大连大学正方教务系统课表适配脚本 (基于 API)
  2. /**
  3. * 解析周次字符串,处理单双周和周次范围。
  4. * @param {string} weekStr - 例如 "1-16周(单)", "1-8周,10-18周"
  5. * @returns {number[]} 解析后的周次数组
  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. * 解析 API 返回的 JSON 数据
  40. */
  41. function parseJsonData(jsonData) {
  42. console.log("JS: parseJsonData 正在解析 JSON 数据...");
  43. if (!jsonData || !Array.isArray(jsonData.kbList)) {
  44. console.warn("JS: JSON 数据结构错误或缺少 kbList 字段。");
  45. return [];
  46. }
  47. const rawCourseList = jsonData.kbList;
  48. const finalCourseList = [];
  49. for (const rawCourse of rawCourseList) {
  50. if (!rawCourse.kcmc || !rawCourse.xm || !rawCourse.cdmc ||
  51. !rawCourse.xqj || !rawCourse.jcs || !rawCourse.zcd) {
  52. continue;
  53. }
  54. const weeksArray = parseWeeks(rawCourse.zcd);
  55. if (weeksArray.length === 0) {
  56. continue;
  57. }
  58. const sectionParts = rawCourse.jcs.split('-');
  59. const startSection = Number(sectionParts[0]);
  60. const endSection = Number(sectionParts[sectionParts.length - 1]);
  61. const day = Number(rawCourse.xqj);
  62. if (isNaN(day) || isNaN(startSection) || isNaN(endSection) || day < 1 || day > 7 || startSection > endSection) {
  63. continue;
  64. }
  65. finalCourseList.push({
  66. name: rawCourse.kcmc.trim(),
  67. teacher: rawCourse.xm.trim(),
  68. position: rawCourse.cdmc.trim(),
  69. day: day,
  70. startSection: startSection,
  71. endSection: endSection,
  72. weeks: weeksArray
  73. });
  74. }
  75. finalCourseList.sort((a, b) =>
  76. a.day - b.day ||
  77. a.startSection - b.startSection ||
  78. a.name.localeCompare(b.name)
  79. );
  80. console.log(`JS: JSON 数据解析完成,共找到 ${finalCourseList.length} 门课程。`);
  81. return finalCourseList;
  82. }
  83. function validateYearInput(input) {
  84. if (/^[0-9]{4}$/.test(input)) {
  85. return false;
  86. } else {
  87. return "请输入四位数字的学年(例如2023)!";
  88. }
  89. }
  90. async function promptUserToStart() {
  91. return await window.AndroidBridgePromise.showAlert(
  92. "大连大学教务系统课表导入",
  93. "导入前请确保您已在浏览器中成功登录教务系统",
  94. "好的,开始导入"
  95. );
  96. }
  97. async function getAcademicYear() {
  98. const currentYear = new Date().getFullYear().toString();
  99. const currentMonth = new Date().getMonth() + 1;
  100. const defaultYear = currentMonth >= 8 ? currentYear : (Number(currentYear) - 1).toString();
  101. return await window.AndroidBridgePromise.showPrompt(
  102. "选择学年",
  103. "请输入要导入课程的起始学年(如2023-2024 应该填2023):",
  104. defaultYear,
  105. "validateYearInput"
  106. );
  107. }
  108. async function selectSemester() {
  109. const semesters = ["第一学期", "第二学期"];
  110. const currentMonth = new Date().getMonth() + 1;
  111. const defaultSemesterIndex = currentMonth >= 8 ? 0 : 1;
  112. const semesterIndex = await window.AndroidBridgePromise.showSingleSelection(
  113. "选择学期",
  114. JSON.stringify(semesters),
  115. defaultSemesterIndex
  116. );
  117. return semesterIndex;
  118. }
  119. /**
  120. * 将选择索引转换为 API 所需的学期码
  121. * DLU: 3 (第一学期), 12 (第二学期)
  122. */
  123. function getSemesterCode(semesterIndex) {
  124. return semesterIndex === 0 ? "3" : "12";
  125. }
  126. /**
  127. * 请求和解析课程数据
  128. */
  129. async function fetchAndParseCourses(academicYear, semesterIndex) {
  130. AndroidBridge.showToast("正在请求课表数据...");
  131. const semesterCode = getSemesterCode(semesterIndex);
  132. // 接口参数
  133. const xnmXqmBody = `xnm=${academicYear}&xqm=${semesterCode}`;
  134. // API URL - 使用了用户截图中确认的 xskbcx_cxXsgrkb.html 路径
  135. const url = "/jwglxt/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151";
  136. console.log(`JS: 发送请求到 ${url}, body: ${xnmXqmBody}`);
  137. const requestOptions = {
  138. "headers": {
  139. "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
  140. },
  141. "body": xnmXqmBody,
  142. "method": "POST",
  143. "credentials": "include"
  144. };
  145. try {
  146. const response = await fetch(url, requestOptions);
  147. if (!response.ok) {
  148. throw new Error(`网络请求失败。状态码: ${response.status}`);
  149. }
  150. const jsonText = await response.text();
  151. let jsonData;
  152. try {
  153. jsonData = JSON.parse(jsonText);
  154. } catch (e) {
  155. AndroidBridge.showToast("数据返回格式错误,可能是您未成功登录或会话已过期。");
  156. return null;
  157. }
  158. const courses = parseJsonData(jsonData);
  159. if (courses.length === 0) {
  160. AndroidBridge.showToast("未找到任何课程数据,请检查所选学年学期是否正确或本学期无课。");
  161. return null;
  162. }
  163. return { courses: courses };
  164. } catch (error) {
  165. AndroidBridge.showToast(`请求或解析失败: ${error.message}`);
  166. return null;
  167. }
  168. }
  169. async function saveCourses(parsedCourses) {
  170. AndroidBridge.showToast(`正在保存 ${parsedCourses.length} 门课程...`);
  171. try {
  172. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses, null, 2));
  173. return true;
  174. } catch (error) {
  175. AndroidBridge.showToast(`课程保存失败: ${error.message}`);
  176. return false;
  177. }
  178. }
  179. // 大连大学统一作息时间 (星期一至星期五)
  180. const TimeSlots = [
  181. { number: 1, startTime: "08:10", endTime: "08:55" },
  182. { number: 2, startTime: "09:00", endTime: "09:45" },
  183. { number: 3, startTime: "10:00", endTime: "10:45" },
  184. { number: 4, startTime: "10:50", endTime: "11:35" },
  185. { number: 5, startTime: "13:15", endTime: "14:00" },
  186. { number: 6, startTime: "14:05", endTime: "14:50" },
  187. { number: 7, startTime: "15:05", endTime: "15:50" },
  188. { number: 8, startTime: "15:55", endTime: "16:40" },
  189. { number: 9, startTime: "16:55", endTime: "17:40" },
  190. { number: 10, startTime: "17:45", endTime: "18:30" }
  191. ];
  192. // 大连大学周末作息时间 (星期六、星期日)
  193. const WeekendTimeSlots = [
  194. { number: 1, startTime: "08:45", endTime: "09:30" },
  195. { number: 2, startTime: "09:40", endTime: "10:25" },
  196. { number: 3, startTime: "10:35", endTime: "11:20" },
  197. { number: 5, startTime: "13:10", endTime: "13:55" },
  198. { number: 6, startTime: "14:00", endTime: "14:45" },
  199. { number: 7, startTime: "14:50", endTime: "15:35" }
  200. ];
  201. function applyCustomTimeToCourses(courses) {
  202. let customizedCount = 0;
  203. const updatedCourses = courses.map((course) => {
  204. // 如果是周末的课程 (星期六或星期日)
  205. if (course.day === 6 || course.day === 7) {
  206. const startSlot = WeekendTimeSlots.find(slot => slot.number === course.startSection);
  207. const endSlot = WeekendTimeSlots.find(slot => slot.number === course.endSection);
  208. if (startSlot && endSlot) {
  209. customizedCount++;
  210. return {
  211. ...course,
  212. isCustomTime: true,
  213. customStartTime: startSlot.startTime,
  214. customEndTime: endSlot.endTime,
  215. };
  216. } else {
  217. console.warn(`JS: 周末课程 ${course.name} 的节次(${course.startSection}-${course.endSection})未命中自定义时间映射,回退为普通节次。`);
  218. }
  219. }
  220. return course;
  221. });
  222. console.log(`JS: 周末自定义时间处理完成,命中 ${customizedCount} 门课程。`);
  223. return updatedCourses;
  224. }
  225. async function importPresetTimeSlots(timeSlots) {
  226. console.log(`JS: 准备导入 ${timeSlots.length} 个预设时间段。`);
  227. if (timeSlots.length > 0) {
  228. AndroidBridge.showToast(`正在导入 ${timeSlots.length} 个预设时间段...`);
  229. try {
  230. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  231. console.log("JS: 预设时间段导入成功。");
  232. } catch (error) {
  233. AndroidBridge.showToast("导入时间段失败: " + error.message);
  234. console.error('JS: Save Time Slots Error:', error);
  235. }
  236. } else {
  237. console.warn("JS: 警告:传入时间段为空,未导入时间段信息。");
  238. }
  239. }
  240. /**
  241. * 导入流程入口
  242. */
  243. async function runImportFlow() {
  244. const alertConfirmed = await promptUserToStart();
  245. if (!alertConfirmed) {
  246. AndroidBridge.showToast("用户取消了导入。");
  247. return;
  248. }
  249. const academicYear = await getAcademicYear();
  250. if (academicYear === null) {
  251. AndroidBridge.showToast("导入已取消。");
  252. return;
  253. }
  254. const semesterIndex = await selectSemester();
  255. if (semesterIndex === null || semesterIndex === -1) {
  256. AndroidBridge.showToast("导入已取消。");
  257. return;
  258. }
  259. const result = await fetchAndParseCourses(academicYear, semesterIndex);
  260. if (result === null) {
  261. return;
  262. }
  263. let { courses } = result;
  264. // 对周末的课程应用自定义时间
  265. courses = applyCustomTimeToCourses(courses);
  266. const saveResult = await saveCourses(courses);
  267. if (!saveResult) {
  268. return;
  269. }
  270. // 导入预设作息时间
  271. await importPresetTimeSlots(TimeSlots);
  272. AndroidBridge.showToast(`课程及作息时间导入成功,共导入 ${courses.length} 门课程!`);
  273. AndroidBridge.notifyTaskCompletion();
  274. }
  275. runImportFlow();