gdipu.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. // 广东轻工职业技术大学教务适配器
  2. // 适配器ID: GDIPU_01
  3. // 提示用户输入学期开始日期
  4. const promptForStartDate = async () => {
  5. try {
  6. const today = new Date();
  7. const defaultDate = today.toISOString().split('T')[0];
  8. const startDate = await window.AndroidBridgePromise.showPrompt(
  9. "请输入学期开始日期",
  10. "格式:YYYY-MM-DD(例如:2026-02-24)",
  11. defaultDate,
  12. "validateDate"
  13. );
  14. if (startDate === null) {
  15. throw new Error("用户取消了输入");
  16. }
  17. if (!/^\d{4}-\d{2}-\d{2}$/.test(startDate)) {
  18. AndroidBridge.showToast("日期格式不正确,请使用YYYY-MM-DD格式");
  19. throw new Error("日期格式不正确");
  20. }
  21. return startDate;
  22. } catch (error) {
  23. console.error("获取开始日期失败:", error);
  24. throw error;
  25. }
  26. };
  27. // 日期验证函数(供AndroidBridge调用)
  28. function validateDate(dateStr) {
  29. if (!dateStr) {
  30. return "日期不能为空";
  31. }
  32. if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
  33. return "日期格式不正确,请使用YYYY-MM-DD格式";
  34. }
  35. const date = new Date(dateStr);
  36. if (isNaN(date.getTime())) {
  37. return "无效的日期";
  38. }
  39. return false;
  40. }
  41. // 获取课程表数据
  42. const fetchTimetable = async (date) => {
  43. try {
  44. const response = await fetch('https://jw.gdipu.edu.cn/jsxsd/framework/main_index_loadkb.jsp', {
  45. method: 'POST',
  46. headers: {
  47. 'Accept': 'text/html, */*; q=0.01',
  48. 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
  49. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  50. 'Origin': 'https://jw.gdipu.edu.cn',
  51. 'Referer': window.location.href,
  52. 'X-Requested-With': 'XMLHttpRequest'
  53. },
  54. credentials: 'include',
  55. body: `rq=${date}`
  56. });
  57. if (!response.ok) {
  58. throw new Error(`HTTP错误: ${response.status}`);
  59. }
  60. const html = await response.text();
  61. // 检查是否有课程
  62. const hasCourses = checkIfHasCourses(html);
  63. return { html, hasCourses };
  64. } catch (error) {
  65. console.error('获取课程表失败:', error);
  66. throw error;
  67. }
  68. };
  69. // 检查HTML中是否有课程
  70. const checkIfHasCourses = (html) => {
  71. const parser = new DOMParser();
  72. const doc = parser.parseFromString(html, 'text/html');
  73. const table = doc.querySelector('form table.kb_table');
  74. if (!table) {
  75. return false;
  76. }
  77. const courseCells = table.querySelectorAll('td p[title]');
  78. return courseCells.length > 0;
  79. };
  80. // 从HTML字符串解析课程表
  81. const parseTimetableFromHTML = (html, date) => {
  82. const courses = [];
  83. const parser = new DOMParser();
  84. const doc = parser.parseFromString(html, 'text/html');
  85. const table = doc.querySelector('form table.kb_table');
  86. if (!table) {
  87. console.error('未找到课程表表格');
  88. return courses;
  89. }
  90. const rows = table.querySelectorAll('tbody tr');
  91. rows.forEach((row) => {
  92. const sectionCell = row.querySelector('td:first-child');
  93. if (!sectionCell) return;
  94. const sectionText = sectionCell.textContent.trim().split('\n')[0];
  95. const sectionInfo = parseSection(sectionText);
  96. // 遍历星期几的列(从第2列到第8列)
  97. for (let colIndex = 1; colIndex <= 7; colIndex++) {
  98. const dayCell = row.querySelector(`td:nth-child(${colIndex + 1})`);
  99. if (!dayCell) continue;
  100. const courseP = dayCell.querySelector('p[title]');
  101. if (!courseP) continue;
  102. const title = courseP.getAttribute('title');
  103. const courseInfo = parseCourseFromTitle(title);
  104. const timeInfo = parseTimeInfo(courseInfo.time || '');
  105. let dayOfWeek;
  106. if (timeInfo.day && timeInfo.day > 0) {
  107. dayOfWeek = timeInfo.day;
  108. } else {
  109. // 列索引映射:1->星期一(1), 2->星期二(2), 3->星期三(3), 4->星期四(4), 5->星期五(5), 6->星期六(6), 7->星期日(7)
  110. dayOfWeek = colIndex;
  111. }
  112. // 将跨节课程拆分为多个单节课程
  113. const startSection = sectionInfo.startSection;
  114. const endSection = sectionInfo.endSection;
  115. for (let section = startSection; section <= endSection; section++) {
  116. const course = {
  117. name: courseInfo.name || '',
  118. teacher: '', // 这个系统似乎没有教师信息
  119. position: courseInfo.location || '',
  120. day: dayOfWeek,
  121. startSection: section,
  122. endSection: section, // 单节课,开始和结束节次相同
  123. weeks: timeInfo.weeks.length > 0 ? timeInfo.weeks : [1] // 暂时使用第1周
  124. };
  125. courses.push(course);
  126. }
  127. }
  128. });
  129. return courses;
  130. };
  131. // 从title属性解析课程信息
  132. const parseCourseFromTitle = (title) => {
  133. const info = {};
  134. const creditMatch = title.match(/课程学分:([\d.]+)/);
  135. const propertyMatch = title.match(/课程属性:([^<]+)/);
  136. const nameMatch = title.match(/课程名称:([^<]+)/);
  137. const timeMatch = title.match(/上课时间:([^<]+)/);
  138. const locationMatch = title.match(/上课地点:([^<]+)/);
  139. const campusMatch = title.match(/上课校区:([^<]+)/);
  140. const groupMatch = title.match(/分组名:([^<]+)/);
  141. if (creditMatch) info.credit = creditMatch[1];
  142. if (propertyMatch) info.property = propertyMatch[1].trim();
  143. if (nameMatch) info.name = nameMatch[1].trim();
  144. if (timeMatch) info.time = timeMatch[1].trim();
  145. if (locationMatch) info.location = locationMatch[1].trim();
  146. if (campusMatch) info.campus = campusMatch[1].trim();
  147. if (groupMatch) info.group = groupMatch[1].trim();
  148. return info;
  149. };
  150. // 解析上课时间字符串
  151. const parseTimeInfo = (timeStr) => {
  152. const result = {
  153. weeks: [],
  154. day: 0,
  155. startSection: 0,
  156. endSection: 0
  157. };
  158. // 解析周数范围
  159. const weekMatch = timeStr.match(/第(\d+)(?:-(\d+))?周/);
  160. if (weekMatch) {
  161. const startWeek = parseInt(weekMatch[1], 10);
  162. if (weekMatch[2]) {
  163. const endWeek = parseInt(weekMatch[2], 10);
  164. for (let week = startWeek; week <= endWeek; week++) {
  165. result.weeks.push(week);
  166. }
  167. } else {
  168. result.weeks.push(startWeek);
  169. }
  170. }
  171. // 解析星期几
  172. const dayMap = {
  173. '星期一': 1,
  174. '星期二': 2,
  175. '星期三': 3,
  176. '星期四': 4,
  177. '星期五': 5,
  178. '星期六': 6,
  179. '星期日': 7
  180. };
  181. for (const [dayStr, dayNum] of Object.entries(dayMap)) {
  182. if (timeStr.includes(dayStr)) {
  183. result.day = dayNum;
  184. break;
  185. }
  186. }
  187. // 解析节次
  188. const sectionMatch = timeStr.match(/\[(\d+)-(\d+)\]/);
  189. if (sectionMatch) {
  190. result.startSection = parseInt(sectionMatch[1], 10);
  191. result.endSection = parseInt(sectionMatch[2], 10);
  192. }
  193. return result;
  194. };
  195. // 解析节次字符串
  196. const parseSection = (sectionStr) => {
  197. const result = {
  198. startSection: 0,
  199. endSection: 0
  200. };
  201. if (sectionStr.includes('-')) {
  202. const parts = sectionStr.split('-');
  203. result.startSection = parseInt(parts[0], 10);
  204. result.endSection = parseInt(parts[1], 10);
  205. } else {
  206. result.startSection = parseInt(sectionStr, 10);
  207. result.endSection = parseInt(sectionStr, 10);
  208. }
  209. return result;
  210. };
  211. // 获取学期配置
  212. const getSemesterConfig = (startDate, totalWeeks) => {
  213. return {
  214. semesterStartDate: startDate,
  215. totalWeeks: totalWeeks
  216. };
  217. };
  218. // 获取时间段配置
  219. const getTimeSlots = (html) => {
  220. // 北区有14节课,使用准确时间配置
  221. return [
  222. { number: 1, startTime: "08:30", endTime: "09:10" },
  223. { number: 2, startTime: "09:15", endTime: "09:55" },
  224. { number: 3, startTime: "10:15", endTime: "10:55" },
  225. { number: 4, startTime: "11:00", endTime: "11:40" },
  226. { number: 5, startTime: "11:45", endTime: "12:25" },
  227. { number: 6, startTime: "13:15", endTime: "13:55" },
  228. { number: 7, startTime: "14:00", endTime: "14:40" },
  229. { number: 8, startTime: "14:45", endTime: "15:25" },
  230. { number: 9, startTime: "15:45", endTime: "16:25" },
  231. { number: 10, startTime: "16:30", endTime: "17:10" },
  232. { number: 11, startTime: "17:15", endTime: "17:55" },
  233. { number: 12, startTime: "19:30", endTime: "20:10" },
  234. { number: 13, startTime: "20:15", endTime: "20:55" },
  235. { number: 14, startTime: "21:00", endTime: "21:40" },
  236. ];
  237. };
  238. // 保存课程数据
  239. const saveSchedule = async (courses, courseConfig, timeSlots) => {
  240. try {
  241. await Promise.allSettled([
  242. window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(courseConfig)),
  243. window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses)),
  244. window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots))
  245. ]);
  246. AndroidBridge.showToast("课程表导入成功!");
  247. return true;
  248. } catch (error) {
  249. console.error("保存课程数据时出错:", error);
  250. AndroidBridge.showToast("课程表导入失败:" + error.message);
  251. return false;
  252. }
  253. };
  254. // 日期增加指定天数
  255. const addDays = (dateStr, days) => {
  256. const date = new Date(dateStr);
  257. date.setDate(date.getDate() + days);
  258. return date.toISOString().split('T')[0];
  259. };
  260. // 判断日期是否是周日(0=周日,1=周一,...,6=周六)
  261. const isSunday = (dateStr) => {
  262. const date = new Date(dateStr);
  263. return date.getDay() === 0; // 0表示周日
  264. };
  265. // 获取多周课程表
  266. const fetchMultiWeekTimetable = async (startDate) => {
  267. const allCourses = [];
  268. //如果起始日期是周日,请求日期就-1
  269. let requestDate = startDate;
  270. if (isSunday(startDate)) {
  271. requestDate = addDays(startDate, -1);
  272. }
  273. let weekCount = 0;
  274. let timeSlots = null;
  275. AndroidBridge.showToast("正在获取课程表数据,请稍候...");
  276. while (true) {
  277. weekCount++;
  278. try {
  279. const { html, hasCourses } = await fetchTimetable(requestDate);
  280. if (weekCount === 1) {
  281. timeSlots = getTimeSlots(html);
  282. }
  283. const weekCourses = parseTimetableFromHTML(html, requestDate);
  284. if (weekCourses.length > 0) {
  285. allCourses.push(...weekCourses);
  286. console.log(`第${weekCount}周: 找到 ${weekCourses.length} 门课程`);
  287. }
  288. if (!hasCourses) {
  289. console.log(`第${weekCount}周: 没有课程,停止获取`);
  290. break;
  291. }
  292. requestDate = addDays(requestDate, 7);
  293. if (weekCount >= 20) {
  294. console.log("达到最大周数限制(20周),停止获取");
  295. break;
  296. }
  297. } catch (error) {
  298. console.error(`获取第${weekCount}周课程表失败:`, error);
  299. AndroidBridge.showToast(`第${weekCount}周获取失败,继续下一周`);
  300. requestDate = addDays(requestDate, 7);
  301. continue;
  302. }
  303. }
  304. return {
  305. courses: allCourses,
  306. totalWeeks: weekCount,
  307. timeSlots: timeSlots || getTimeSlots('')
  308. };
  309. };
  310. // 主函数
  311. (async () => {
  312. try {
  313. AndroidBridge.showToast("正在启动GDIPU课程表导入...");
  314. const startDate = await promptForStartDate();
  315. const { courses, totalWeeks, timeSlots } = await fetchMultiWeekTimetable(startDate);
  316. if (courses.length === 0) {
  317. AndroidBridge.showToast("未找到任何课程信息");
  318. throw new Error("未找到课程信息");
  319. }
  320. console.log(`总共找到 ${courses.length} 门课程,共 ${totalWeeks} 周`);
  321. const courseConfig = getSemesterConfig(startDate, totalWeeks);
  322. const success = await saveSchedule(courses, courseConfig, timeSlots);
  323. if (success) {
  324. AndroidBridge.notifyTaskCompletion();
  325. } else {
  326. throw new Error("保存课程数据失败");
  327. }
  328. } catch (error) {
  329. console.error("导入课程表时出错:", error);
  330. AndroidBridge.showToast("导入课程表失败:" + error.message);
  331. }
  332. })();