WHCB.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // 节次对应的时间映射表
  2. const TimeSlots = {
  3. 1: { start: "08:10", end: "08:55" },
  4. 2: { start: "09:05", end: "09:50" },
  5. 3: { start: "10:10", end: "10:55" },
  6. 4: { start: "11:05", end: "11:50" },
  7. 5: { start: "12:00", end: "12:45" },
  8. 6: { start: "13:05", end: "13:50" },
  9. 7: { start: "14:00", end: "14:45" },
  10. 8: { start: "14:55", end: "15:40" },
  11. 9: { start: "16:00", end: "16:45" },
  12. 10: { start: "16:55", end: "17:40" },
  13. 11: { start: "19:00", end: "19:45" },
  14. 12: { start: "19:55", end: "20:40" }
  15. };
  16. // 解析课程名称
  17. function parseCourseName(text) {
  18. if (!text) return "";
  19. const match = text.match(/^([^\[\]]+)/);
  20. return match ? match[1].trim() : text.trim();
  21. }
  22. // 解析教室信息
  23. function parseClassroom(text) {
  24. if (!text) return "";
  25. const parts = text.split(',');
  26. if (parts.length >= 3) return parts[parts.length - 1].trim();
  27. if (parts.length === 2) return parts[1].trim();
  28. return text.trim();
  29. }
  30. // 解析节次范围
  31. function parseSections(roomText) {
  32. if (!roomText) return { start: 1, end: 1 };
  33. const match = roomText.match(/(\d+)-(\d+)\s*[节]?$/);
  34. if (match) return { start: parseInt(match[1]), end: parseInt(match[2]) };
  35. const parts = roomText.split(',');
  36. for (const part of parts) {
  37. const sectionMatch = part.match(/(\d+)-(\d+)/);
  38. if (sectionMatch && !part.includes('周')) {
  39. return { start: parseInt(sectionMatch[1]), end: parseInt(sectionMatch[2]) };
  40. }
  41. }
  42. return { start: 1, end: 1 };
  43. }
  44. // 解析周次信息
  45. function parseWeeksFromRoom(roomText) {
  46. if (!roomText) return [];
  47. const weeks = new Set();
  48. const weekPatterns = roomText.match(/(\d+)(?:-(\d+))?周/g);
  49. if (weekPatterns) {
  50. weekPatterns.forEach(pattern => {
  51. const match = pattern.match(/(\d+)(?:-(\d+))?周/);
  52. if (match) {
  53. const startWeek = parseInt(match[1]);
  54. const endWeek = match[2] ? parseInt(match[2]) : startWeek;
  55. for (let week = startWeek; week <= endWeek; week++) weeks.add(week);
  56. }
  57. });
  58. }
  59. return Array.from(weeks).sort((a, b) => a - b);
  60. }
  61. // 提取课程数据
  62. function extractCourses() {
  63. const courses = [];
  64. const courseCells = document.querySelectorAll('td[data-role="item"]');
  65. courseCells.forEach(cell => {
  66. const courseDivs = cell.querySelectorAll('.mtt_arrange_item');
  67. if (courseDivs.length === 0) return;
  68. const day = parseInt(cell.getAttribute('data-week') || "1");
  69. const beginUnit = parseInt(cell.getAttribute('data-begin-unit') || "1");
  70. const endUnit = parseInt(cell.getAttribute('data-end-unit') || beginUnit);
  71. const startSection = Math.min(beginUnit, endUnit);
  72. const endSection = Math.max(beginUnit, endUnit);
  73. courseDivs.forEach(courseDiv => {
  74. const detailDiv = courseDiv.querySelector('.mtt_item_kcmc');
  75. if (!detailDiv) return;
  76. const courseNameNode = detailDiv.firstChild;
  77. const rawCourseName = courseNameNode?.nodeValue?.trim() || "";
  78. if (!rawCourseName) return;
  79. const teacherElement = detailDiv.querySelector('.mtt_item_jxbmc');
  80. const teacher = teacherElement?.innerText?.trim() || "";
  81. const roomElement = detailDiv.querySelector('.mtt_item_room');
  82. const rawRoomText = roomElement?.innerText?.trim() || "";
  83. const weeks = parseWeeksFromRoom(rawRoomText);
  84. if (weeks.length === 0) return;
  85. const position = parseClassroom(rawRoomText);
  86. let finalStartSection = startSection;
  87. let finalEndSection = endSection;
  88. if (startSection === 1 && endSection === 1) {
  89. const sections = parseSections(rawRoomText);
  90. finalStartSection = sections.start;
  91. finalEndSection = sections.end;
  92. }
  93. const course = {
  94. name: parseCourseName(rawCourseName),
  95. teacher: teacher,
  96. position: position,
  97. day: day,
  98. startSection: finalStartSection,
  99. endSection: finalEndSection,
  100. weeks: weeks
  101. };
  102. if (course.name && course.weeks.length > 0) courses.push(course);
  103. });
  104. });
  105. return courses;
  106. }
  107. // 合并重复课程
  108. function mergeCourses(courses) {
  109. const courseMap = new Map();
  110. courses.forEach(course => {
  111. const key = `${course.name}-${course.teacher}-${course.day}-${course.startSection}-${course.endSection}-${course.position}`;
  112. if (courseMap.has(key)) {
  113. const existing = courseMap.get(key);
  114. const mergedWeeks = [...new Set([...existing.weeks, ...course.weeks])].sort((a, b) => a - b);
  115. existing.weeks = mergedWeeks;
  116. courseMap.set(key, existing);
  117. } else {
  118. courseMap.set(key, { ...course });
  119. }
  120. });
  121. return Array.from(courseMap.values());
  122. }
  123. // 排序课程
  124. function sortCourses(courses) {
  125. return courses.sort((a, b) => {
  126. if (a.day !== b.day) return a.day - b.day;
  127. if (a.startSection !== b.startSection) return a.startSection - b.startSection;
  128. return a.endSection - b.endSection;
  129. });
  130. }
  131. // 生成时间段数据
  132. function generateTimeSlots() {
  133. return Object.entries(TimeSlots).map(([number, time]) => ({
  134. number: parseInt(number),
  135. startTime: time.start,
  136. endTime: time.end
  137. }));
  138. }
  139. // 生成配置数据
  140. function generateConfig(courses) {
  141. let maxWeek = 0;
  142. courses.forEach(course => {
  143. course.weeks.forEach(week => {
  144. if (week > maxWeek) maxWeek = week;
  145. });
  146. });
  147. return {
  148. semesterTotalWeeks: maxWeek > 0 ? maxWeek : 20,//不确定以后会不会改,先留着
  149. defaultClassDuration: 45,
  150. defaultBreakDuration: 10,
  151. firstDayOfWeek: 1
  152. };
  153. }
  154. //登录检测
  155. async function login(){
  156. const promptMessage =`
  157. 使用前请注意:
  158. 1、开始导入前,请确保自己已经登录并进入课表
  159. 2、导入过程中请保持网络连接畅通
  160. 3、导入完成后如学期周数等信息错误请前往设置调整
  161. 4、导入完成后请前往设置自行设置开学时间
  162. 5、本工具仅支持导入当前学期课程
  163. 6、学校服务器很烂,课表数据可能加载较慢,请耐心等待
  164. 7、如果没有进入界面请前往学校学生门户登录并在新教务系统中打开课表后尝试导入
  165. `;
  166. const wspc = "authserver.wspc.edu.cn";
  167. const timeTable = "jw.wspc.edu.cn";
  168. const currentHost = window.location.host;
  169. const confirmed = await window.AndroidBridgePromise.showAlert(
  170. "使用前请注意",
  171. promptMessage,
  172. "好的"
  173. );
  174. if (confirmed) {
  175. if (currentHost == wspc) {
  176. AndroidBridge.showToast("请先登录");
  177. return false;
  178. }
  179. else if (currentHost ==timeTable) {
  180. return true;
  181. }
  182. else
  183. AndroidBridge.showToast("请在学校的网站内导入");
  184. }
  185. else {
  186. AndroidBridge.showToast("取消导入操作");
  187. return false;
  188. }
  189. }
  190. async function saveCourses(parsedCourses) {
  191. try {
  192. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
  193. AndroidBridge.showToast(`成功导入 ${parsedCourses.length} 门课程!`);
  194. return true;
  195. } catch (error) {
  196. AndroidBridge.showToast(`保存失败: ${error.message}`);
  197. return false;
  198. }
  199. }
  200. async function importPresetTimeSlots(timeSlots) {
  201. try {
  202. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  203. AndroidBridge.showToast("预设时间段导入成功!");
  204. return true; // 添加返回值
  205. } catch (error) {
  206. AndroidBridge.showToast("导入时间段失败: " + error.message);
  207. return false; // 添加返回值
  208. }
  209. }
  210. async function saveConfig(config) {
  211. try {
  212. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  213. AndroidBridge.showToast("课表配置更新成功!");
  214. return true;
  215. } catch (error) {
  216. AndroidBridge.showToast("保存配置失败: " + error.message);
  217. return false;
  218. }
  219. }
  220. async function runImportFlow() {
  221. const loginwindows = await login();
  222. if(!loginwindows){
  223. return;
  224. }
  225. // 提取课程数据
  226. const rawCourses = extractCourses();
  227. if(rawCourses.length === 0){
  228. AndroidBridge.showToast("未找到任何课程! 请确认已登录并进入课表页面。");
  229. return;
  230. }
  231. const mergedCourses = mergeCourses(rawCourses);
  232. if(mergedCourses.length === 0){
  233. AndroidBridge.showToast("课程合并失败,未生成任何课程数据!");
  234. return;
  235. }
  236. const finalCourses = sortCourses(mergedCourses);
  237. if(finalCourses.length === 0){
  238. AndroidBridge.showToast("课程排序失败,未生成任何课程数据!");
  239. return;
  240. }
  241. // 生成时间段数据
  242. const timeSlots = generateTimeSlots();
  243. if(timeSlots.length === 0){
  244. AndroidBridge.showToast("时间段生成失败,未生成任何时间段数据!");
  245. return;
  246. }
  247. // 生成配置数据
  248. const config = generateConfig(finalCourses);
  249. // 输出课程数据结构
  250. const saveResult = await saveCourses(finalCourses);
  251. if(!saveResult){
  252. return;
  253. }
  254. // 输出时间段数据结构
  255. const timeSlotResult = await importPresetTimeSlots(timeSlots);
  256. if(!timeSlotResult){
  257. return;
  258. }
  259. // 输出课表配置数据结构
  260. const configResult = await saveConfig(config);
  261. if(!configResult){
  262. return;
  263. }
  264. AndroidBridge.showToast("成功导入课表!请前往设置调整开学时间等信息!");
  265. AndroidBridge.notifyTaskCompletion();
  266. }
  267. // 执行
  268. runImportFlow();