neu.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // 文件: school.js
  2. // 1. 显示一个公告信息弹窗
  3. async function demoAlert() {
  4. try {
  5. console.log("即将显示公告弹窗...");
  6. const confirmed = await window.AndroidBridgePromise.showAlert(
  7. "注意",
  8. "教务系统网址仅在校园网/连接校内vpn环境下可访问,无法进入时请检查网络连接,本适配仅适配东北大学本科生新教务系统,其他院校或研究生用户请谨慎使用。如有问题请联系开发者反馈。",
  9. "我知道了"
  10. );
  11. if (confirmed) {
  12. return true; // 成功时返回 true
  13. } else {
  14. return false; // 用户取消时返回 false
  15. }
  16. } catch (error) {
  17. console.error("显示公告弹窗时发生错误:", error);
  18. AndroidBridge.showToast("Alert:显示弹窗出错!" + error.message);
  19. return false; // 出现错误时也返回 false
  20. }
  21. }
  22. // 2. 从课表页面中提取课程数据
  23. async function extractCoursesFromPage() {
  24. const iframe = document.querySelector('iframe');
  25. const lessons = [];
  26. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  27. const time = iframeDoc.querySelector('.kbappTimeXQText')
  28. const time_text = time.textContent;
  29. const dayCols = iframeDoc.querySelectorAll('.kbappTimetableDayColumnRoot');
  30. dayCols.forEach((dayCol, dayIndex) => {// 遍历每一列
  31. const timeSlots = dayCol.children;
  32. const day = dayIndex >= 1 ? dayIndex : 7; // 课表第一天为星期日
  33. let startSection = 0;
  34. let endSection = 0;
  35. for (let slot of timeSlots) {
  36. const flexValue = slot.style.flex;
  37. const nums = parseInt(flexValue.split(' ')[0]);
  38. startSection = endSection+1;
  39. endSection = startSection + nums - 1;
  40. if (slot.classList.contains('kbappTimetableDayColumnConflictContainer')) {
  41. const courseItem = slot.querySelector('.kbappTimetableCourseRenderCourseItem');
  42. const infoTexts = courseItem.querySelectorAll('.kbappTimetableCourseRenderCourseItemInfoText');
  43. let name,details;
  44. infoTexts.forEach((text, idx) => {
  45. if (idx === 0) name = text.textContent.trim();
  46. else if (idx === 1) details = parseCourseDetails(text.textContent.trim());//weeks,teacher,position
  47. else if (idx === 2) return
  48. });
  49. lessons.push({name: name, teacher: details.teacher, position: details.position, day: day, startSection: startSection, endSection: endSection,weeks: details.weeks});
  50. }
  51. }
  52. console.log("信息提取中");
  53. });
  54. return {lessons:lessons,time_text:time_text};
  55. }
  56. // 2.1 解析课程详情字符串,提取周次、教师和地点信息
  57. function parseCourseDetails(detailStr) {
  58. // 匹配所有周次模式
  59. const weekPattern = /(\d+-\d+周(?:\([单双]\))?|\d+周(?:\([单双]\))?)/g;
  60. const weekMatches = detailStr.match(weekPattern);
  61. let weeks = '';
  62. let remaining = detailStr;
  63. if (weekMatches) {
  64. // 提取所有周次部分
  65. weeks = weekMatches.join(',');
  66. // 从原字符串中移除周次部分
  67. weekMatches.forEach(match => {
  68. remaining = remaining.replace(match, '');
  69. });
  70. }
  71. // 按空格分割剩余部分
  72. const parts = remaining.trim().split(/\s+/).filter(p => p);
  73. let teacher = '';
  74. let position = '';
  75. if (parts.length > 0) {
  76. teacher = parts[0];
  77. if (parts.length > 1) {
  78. position = parts.slice(1).join(' '); // 修正这一行
  79. }
  80. }
  81. // 清理教师名中的多余逗号
  82. teacher = teacher.replace(/^[,,]/, '').replace(/[,,]$/, '');
  83. return {
  84. weeks: parseWeeksString(weeks),
  85. teacher: teacher.trim(),
  86. position: position.trim()
  87. };
  88. }
  89. // 2.2将周次文字提取成数组
  90. function parseWeeksString(weeksStr) {
  91. if (!weeksStr) return [];
  92. const result = [];
  93. const weekParts = weeksStr.split(/[,,]/).map(part => part.trim());
  94. weekParts.forEach(part => {
  95. // 匹配单个数字周
  96. const singleMatch = part.match(/^(\d+)周(?:\(([单双])\))?$/);
  97. if (singleMatch) {
  98. const num = parseInt(singleMatch[1]);
  99. const type = singleMatch[2];
  100. if (!type || (type === '单' && num % 2 === 1) || (type === '双' && num % 2 === 0)) {
  101. result.push(num);
  102. }
  103. return;
  104. }
  105. // 匹配范围周
  106. const rangeMatch = part.match(/^(\d+)-(\d+)周(?:\(([单双])\))?$/);
  107. if (rangeMatch) {
  108. const start = parseInt(rangeMatch[1]);
  109. const end = parseInt(rangeMatch[2]);
  110. const type = rangeMatch[3];
  111. if (!type) {
  112. for (let i = start; i <= end; i++) result.push(i);
  113. } else if (type === '单') {
  114. for (let i = start; i <= end; i++) {
  115. if (i % 2 === 1) result.push(i);
  116. }
  117. } else if (type === '双') {
  118. for (let i = start; i <= end; i++) {
  119. if (i % 2 === 0) result.push(i);
  120. }
  121. }
  122. }
  123. });
  124. return [...new Set(result)].sort((a, b) => a - b);
  125. }
  126. // 2.3 解析学期字符串,返回对应的开学日期
  127. function parseSemesterToDate(semesterStr) {
  128. // 使用正则表达式提取年份和学期信息
  129. const regex = /(\d{4})-(\d{4})学年(春季|秋季)学期/;
  130. const match = semesterStr.match(regex);
  131. if (!match) {
  132. throw new Error('学期字符串格式不正确,应为:"XXXX-XXXX学年春季/秋季学期"');
  133. }
  134. const startYear = parseInt(match[1]); // 前一个年份
  135. const endYear = parseInt(match[2]); // 后一个年份
  136. const season = match[3]; // 春季或秋季
  137. // 验证年份格式是否正确(后一年份应比前一年份大1)
  138. if (endYear !== startYear + 1) {
  139. throw new Error('年份格式不正确,后一年份应比前一年份大1');
  140. }
  141. let resultDate;
  142. if (season === '春季') {
  143. // 春季学期:使用后一个年份的3月1日
  144. resultDate = `${endYear}-03-01`;
  145. } else if (season === '秋季') {
  146. // 秋季学期:使用前一个年份的9月1日
  147. resultDate = `${startYear}-09-01`;
  148. } else {
  149. throw new Error('学期类型不正确,应为"春季"或"秋季"');
  150. }
  151. return resultDate;
  152. }
  153. // 3. 导入课程数据
  154. async function SaveCourses(lessons) {
  155. console.log("正在准备测试课程数据...");
  156. const testCourses = lessons;
  157. try {
  158. console.log("正在尝试导入课程...");
  159. const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(testCourses));
  160. if (result === true) {
  161. console.log("课程导入成功!");
  162. } else {
  163. console.log("课程导入未成功,结果:" + result);
  164. AndroidBridge.showToast("测试课程导入失败,请查看日志。");
  165. }
  166. } catch (error) {
  167. console.error("导入课程时发生错误:", error);
  168. AndroidBridge.showToast("导入课程失败: " + error.message);
  169. }
  170. }
  171. // 4. 导入预设时间段
  172. async function importPresetTimeSlots() {
  173. console.log("正在准备预设时间段数据...");
  174. const presetTimeSlots = [
  175. { "number": 1, "startTime": "08:30", "endTime": "09:15" },
  176. { "number": 2, "startTime": "09:25", "endTime": "10:10" },
  177. { "number": 3, "startTime": "10:30", "endTime": "11:15" },
  178. { "number": 4, "startTime": "11:25", "endTime": "12:10" },
  179. { "number": 5, "startTime": "14:00", "endTime": "14:45" },
  180. { "number": 6, "startTime": "14:55", "endTime": "15:40" },
  181. { "number": 7, "startTime": "16:00", "endTime": "16:45" },
  182. { "number": 8, "startTime": "16:55", "endTime": "17:40" },
  183. { "number": 9, "startTime": "18:30", "endTime": "19:15" },
  184. { "number": 10, "startTime": "19:25", "endTime": "20:10" },
  185. { "number": 11, "startTime": "20:30", "endTime": "21:15" },
  186. { "number": 12, "startTime": "21:15", "endTime": "22:10" },
  187. ];
  188. try {
  189. console.log("正在尝试导入预设时间段...");
  190. const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  191. if (result === true) {
  192. console.log("预设时间段导入成功!");
  193. } else {
  194. console.log("预设时间段导入未成功,结果:" + result);
  195. window.AndroidBridge.showToast("测试时间段导入失败,请查看日志。");
  196. }
  197. } catch (error) {
  198. console.error("导入时间段时发生错误:", error);
  199. window.AndroidBridge.showToast("导入时间段失败: " + error.message);
  200. }
  201. }
  202. // 5. 导入课表配置
  203. async function SaveConfig(time_text) {
  204. console.log("正在准备配置数据...");
  205. startDate = parseSemesterToDate(time_text);
  206. // 注意:只传入要修改的字段,其他字段(如 semesterTotalWeeks)会使用 Kotlin 模型中的默认值
  207. const courseConfigData = {
  208. "semesterStartDate": startDate,
  209. "semesterTotalWeeks": 18,
  210. "defaultClassDuration": 45,
  211. "defaultBreakDuration": 10,
  212. "firstDayOfWeek": 7
  213. };
  214. try {
  215. console.log("正在尝试导入课表配置...");
  216. const configJsonString = JSON.stringify(courseConfigData);
  217. const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
  218. if (result === true) {
  219. console.log("课表配置导入成功!");
  220. } else {
  221. console.log("课表配置导入未成功,结果:" + result);
  222. AndroidBridge.showToast("测试配置导入失败,请查看日志。");
  223. }
  224. } catch (error) {
  225. console.error("导入配置时发生错误:", error);
  226. AndroidBridge.showToast("导入配置失败: " + error.message);
  227. }
  228. }
  229. /**
  230. * 编排这些异步操作,并在用户取消时停止后续执行。
  231. */
  232. async function runAllDemosSequentially() {
  233. AndroidBridge.showToast("所有演示将按顺序开始...");
  234. // 1. 提示公告
  235. const alertResult = await demoAlert();
  236. if (!alertResult) {
  237. console.log("用户取消了 Alert 演示,停止后续执行。");
  238. return; // 用户取消,立即退出函数
  239. }
  240. console.log("所有弹窗演示已完成。");
  241. AndroidBridge.showToast("所有弹窗演示已完成!");
  242. // 以下是数据导入,与用户交互无关,可以继续
  243. const PageInfo = await extractCoursesFromPage();//从课表页面中提取课程数据
  244. const lessons = PageInfo.lessons;
  245. const time_text = PageInfo.time_text;
  246. await SaveCourses(lessons);//保存课程数据到数据库
  247. await importPresetTimeSlots();//导入预设时间槽
  248. await SaveConfig(time_text);//保存底层配置
  249. // 发送最终的生命周期完成信号
  250. AndroidBridge.notifyTaskCompletion();
  251. }
  252. // 启动所有演示
  253. runAllDemosSequentially();