cup_02.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // resources/CUP/cup_02.js
  2. // 中国石油大学(北京)拾光课程表适配脚本
  3. // https://gmis.cup.edu.cn/gmis/student/default/index
  4. // 教务平台:南京南软
  5. // 适配开发者:larryyan
  6. // ==========================================
  7. // 1. 全局配置与工具类
  8. // ==========================================
  9. const CONFIG = {
  10. campuses: {
  11. MainCampus: {
  12. name: "主校区",
  13. starts: { morning: "08:00", afternoon: "13:30", evening: "18:30" },
  14. counts: { morning: 4, afternoon: 4, evening: 3 },
  15. breaks: { short: 5, long: 30, longAfter: { morning: 2, afternoon: 2, evening: 0 } },
  16. classMins: 45
  17. },
  18. Karamay: {
  19. name: "克拉玛依校区",
  20. starts: { morning: "09:30", afternoon: "16:00", evening: "20:30" },
  21. counts: { morning: 5, afternoon: 4, evening: 3 },
  22. breaks: { short: 5, long: 20, longAfter: { morning: 2, afternoon: 2, evening: 0 } },
  23. classMins: 45
  24. }
  25. }
  26. };
  27. const Utils = {
  28. toast: (msg) => { if (typeof AndroidBridge !== 'undefined') AndroidBridge.showToast(msg); },
  29. timeToMins: (timeStr) => { const [h, m] = timeStr.split(':').map(Number); return h * 60 + m; },
  30. minsToTime: (mins) => `${Math.floor(mins / 60).toString().padStart(2, '0')}:${(mins % 60).toString().padStart(2, '0')}`,
  31. parseWeeks: (weekStr) => {
  32. let weeks = [];
  33. let isSingle = weekStr.includes('单'), isDouble = weekStr.includes('双');
  34. (weekStr.match(/\d+-\d+|\d+/g) || []).forEach(m => {
  35. let [start, end] = m.includes('-') ? m.split('-').map(Number) : [Number(m), Number(m)];
  36. for (let i = start; i <= end; i++) {
  37. if ((isSingle && i % 2 === 0) || (isDouble && i % 2 !== 0)) continue;
  38. weeks.push(i);
  39. }
  40. });
  41. return [...new Set(weeks)].sort((a, b) => a - b);
  42. }
  43. };
  44. function validateDateInput(input) {
  45. return /^\d{4}[-\/\.]\d{2}[-\/\.]\d{2}$/.test(input) ? false : "格式错误,例: 2025-09-01";
  46. }
  47. // ==========================================
  48. // 2. 核心业务流程
  49. // ==========================================
  50. async function selectCampus() {
  51. const ids = Object.keys(CONFIG.campuses);
  52. const labels = ids.map(id => CONFIG.campuses[id].name);
  53. const index = await window.AndroidBridgePromise.showSingleSelection("选择所在校区", JSON.stringify(labels), 0);
  54. return index !== null ? ids[index] : null;
  55. }
  56. async function getTermCode() {
  57. if (typeof $ === 'undefined') throw new Error("缺少环境,请在课表页执行");
  58. const data = await $.ajax({ type: 'get', url: '/gmis/default/bindterm', dataType: 'json', cache: false });
  59. if (!data || !data.length) throw new Error("获取学期列表失败");
  60. const texts = data.map(i => i.termname);
  61. const codes = data.map(i => i.termcode);
  62. const defaultIdx = data.findIndex(i => i.selected) || 0;
  63. const index = await window.AndroidBridgePromise.showSingleSelection("选择导入学期", JSON.stringify(texts), defaultIdx);
  64. return index !== null ? codes[index] : null;
  65. }
  66. async function fetchCourseData(termCode) {
  67. Utils.toast("正在解析数据...");
  68. const data = await $.ajax({ type: 'post', url: "py_kbcx_ew", data: { kblx: 'xs', termcode: termCode }, dataType: 'json', cache: false });
  69. if (!data || !data.rows) throw new Error("接口数据异常");
  70. return data.rows;
  71. }
  72. async function processAndSaveCourses(rawData, campusId) {
  73. const isKaramay = campusId === "Karamay";
  74. let allBlocks = [], mergedCourses = [];
  75. const getSec = (jcid) => {
  76. if (jcid >= 11 && jcid <= 15) return jcid - 10;
  77. if (jcid >= 21 && jcid <= 24) return jcid - 20 + (isKaramay ? 5 : 4);
  78. if (jcid >= 31 && jcid <= 33) return jcid - 30 + (isKaramay ? 9 : 8);
  79. return 1;
  80. };
  81. rawData.forEach(row => {
  82. if (!isKaramay && row.jcid === 15) return;
  83. const currentSec = getSec(row.jcid);
  84. for (let d = 1; d <= 7; d++) {
  85. if (!row['z' + d]) continue;
  86. row['z' + d].split(/<br\s*\/?>/i).forEach(part => {
  87. const match = part.match(/(.*?)\[(.*?)\]([^\[]*)(?:\[(.*?)\])?$/);
  88. if (match) allBlocks.push({ name: match[1].trim(), weekStr: match[2].trim(), weeks: Utils.parseWeeks(match[2]), teacher: (match[3] || "").trim(), position: (match[4] || "未知地点").trim(), day: d, section: currentSec });
  89. });
  90. }
  91. });
  92. allBlocks.forEach(b => {
  93. let exist = mergedCourses.find(c => c.day === b.day && c.name === b.name && c.teacher === b.teacher && c.weekStr === b.weekStr && c.endSection === b.section - 1);
  94. exist ? exist.endSection = b.section : mergedCourses.push({ ...b, startSection: b.section, endSection: b.section });
  95. });
  96. mergedCourses.forEach(c => delete c.weekStr);
  97. if (!(await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(mergedCourses)))) {
  98. throw new Error("课程保存失败");
  99. }
  100. }
  101. async function generateAndSaveTimeSlots(campusId) {
  102. const c = CONFIG.campuses[campusId];
  103. let slots = [], secNum = 1;
  104. ["morning", "afternoon", "evening"].forEach(period => {
  105. let mins = Utils.timeToMins(c.starts[period]);
  106. for (let i = 1; i <= c.counts[period]; i++) {
  107. const start = Utils.minsToTime(mins);
  108. mins += c.classMins;
  109. slots.push({ number: secNum++, startTime: start, endTime: Utils.minsToTime(mins) });
  110. if (i < c.counts[period]) mins += (i === c.breaks.longAfter[period] ? c.breaks.long : c.breaks.short);
  111. }
  112. });
  113. if (!(await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(slots)))) {
  114. throw new Error("时间段保存失败");
  115. }
  116. }
  117. async function promptAndSaveConfig() {
  118. let date = await window.AndroidBridgePromise.showPrompt("输入开学日期", "格式: YYYY-MM-DD", "2026-03-09", "validateDateInput");
  119. date = date ? date.trim().replace(/[\/\.]/g, '-') : "2026-03-09";
  120. const cfg = { semesterStartDate: date, semesterTotalWeeks: 25, defaultClassDuration: 45, defaultBreakDuration: 5, firstDayOfWeek: 1 };
  121. if (!(await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(cfg)))) {
  122. throw new Error("配置保存失败");
  123. }
  124. }
  125. // ==========================================
  126. // 3. 主流程引擎 (集中错误处理)
  127. // ==========================================
  128. async function runImportFlow() {
  129. try {
  130. if (!(await window.AndroidBridgePromise.showAlert("导入通知", "请确保已在网页查看到课表。", "开始"))) return;
  131. const campusId = await selectCampus();
  132. if (!campusId) return;
  133. const termCode = await getTermCode();
  134. if (!termCode) return;
  135. const rawData = await fetchCourseData(termCode);
  136. await processAndSaveCourses(rawData, campusId);
  137. await generateAndSaveTimeSlots(campusId);
  138. await promptAndSaveConfig();
  139. Utils.toast("🎉 课表导入成功!");
  140. if (typeof AndroidBridge !== 'undefined') AndroidBridge.notifyTaskCompletion();
  141. } catch (err) {
  142. console.error("流程中止:", err);
  143. Utils.toast("导入中断: " + err.message);
  144. }
  145. }
  146. runImportFlow();