jyvtc.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. // 济源职业技术学院_拾光课程表适配脚本
  2. // 非该大学开发者适配,开发者无法及时发现问题
  3. // 出现问题请提联系开发者或者提交pr更改,这更加快速
  4. function parseWeeks(weekStr) {
  5. const weeks = [];
  6. const isDoubleWeek = weekStr.includes('双周');
  7. const isSingleWeek = weekStr.includes('单周');
  8. let cleanWeekStr = weekStr.replace('双周', '').replace('单周', '').trim();
  9. const parts = cleanWeekStr.split(',');
  10. for (const part of parts) {
  11. let currentPart = part.trim();
  12. if (currentPart.includes('-')) {
  13. const [start, end] = currentPart.split('-').map(Number);
  14. for (let i = start; i <= end; i++) {
  15. if (isDoubleWeek) {
  16. if (i % 2 === 0) weeks.push(i);
  17. } else if (isSingleWeek) {
  18. if (i % 2 !== 0) weeks.push(i);
  19. } else {
  20. weeks.push(i);
  21. }
  22. }
  23. } else if (!isNaN(Number(currentPart)) && currentPart !== '') {
  24. weeks.push(Number(currentPart));
  25. }
  26. }
  27. return [...new Set(weeks)].sort((a, b) => a - b);
  28. }
  29. function parseCourseString(cellContent) {
  30. const courses = [];
  31. const content = cellContent.replace(/<\/?ul>/g, '').trim();
  32. const courseLines = content.split(/<br\s*\/?>/i);
  33. for (const line of courseLines) {
  34. const text = line.trim();
  35. if (!text) continue;
  36. const match = text.match(/(.+?)\s+([^\s(]+)\s*\(([^()]+)\)/);
  37. if (match) {
  38. const name = match[1].trim();
  39. const teacher = match[2].trim();
  40. const fullContent = match[3].trim();
  41. const lastSpaceIndex = fullContent.lastIndexOf(' ');
  42. if (lastSpaceIndex !== -1) {
  43. const weeksStr = fullContent.substring(0, lastSpaceIndex).trim();
  44. const position = fullContent.substring(lastSpaceIndex + 1).trim();
  45. if (name && teacher && position && weeksStr) {
  46. courses.push({
  47. name: name,
  48. teacher: teacher,
  49. position: position,
  50. weeks: parseWeeks(weeksStr)
  51. });
  52. }
  53. }
  54. }
  55. }
  56. return courses;
  57. }
  58. function transformSchedule(htmlString) {
  59. console.log("JS: transformSchedule 正在解析 HTML...");
  60. const rowsMatch = htmlString.match(/<tr class="mykb"[\s\S]*?<\/tr>/g);
  61. if (!rowsMatch) {
  62. console.warn("JS: transformSchedule 未找到任何课程行 (tr class='mykb')");
  63. return [];
  64. }
  65. const tempCourseList = [];
  66. const sectionMap = {};
  67. for (let i = 0; i < rowsMatch.length; i++) {
  68. const row = rowsMatch[i];
  69. const sectionMatch = row.match(/<td[^>]*?>[^<]*?(\d+)(?:\([^)]*\))?/i);
  70. if (sectionMatch && sectionMatch[1]) {
  71. sectionMap[i] = Number(sectionMatch[1]);
  72. }
  73. }
  74. for (let rowIndex = 0; rowIndex < rowsMatch.length; rowIndex++) {
  75. const row = rowsMatch[rowIndex];
  76. const currentSection = sectionMap[rowIndex];
  77. if (!currentSection) continue;
  78. const allCells = row.match(/<td\s+[^>]*?>[\s\S]*?<\/td>/g);
  79. if (!allCells) continue;
  80. let courseCells;
  81. if (allCells.length === 9) {
  82. courseCells = allCells.slice(2, 9);
  83. } else if (allCells.length === 8) {
  84. courseCells = allCells.slice(1, 8);
  85. } else {
  86. continue;
  87. }
  88. if (courseCells.length !== 7) {
  89. continue;
  90. }
  91. let dayOfWeek = 1;
  92. for (const cellHtml of courseCells) {
  93. const cellContentMatch = cellHtml.match(/<ul>([\s\S]*?)<\/ul>/);
  94. if (cellContentMatch) {
  95. const courseLinesContent = cellContentMatch[1];
  96. const coursesInCell = parseCourseString(courseLinesContent);
  97. for (const course of coursesInCell) {
  98. if (dayOfWeek >= 1 && dayOfWeek <= 7) {
  99. tempCourseList.push({
  100. name: course.name,
  101. teacher: course.teacher,
  102. position: course.position,
  103. day: dayOfWeek,
  104. startSection: currentSection,
  105. endSection: currentSection,
  106. weeks: course.weeks
  107. });
  108. }
  109. }
  110. }
  111. dayOfWeek++;
  112. }
  113. }
  114. const finalCourseList = [];
  115. tempCourseList.sort((a, b) =>
  116. a.day - b.day ||
  117. a.name.localeCompare(b.name) ||
  118. a.teacher.localeCompare(b.teacher) ||
  119. a.position.localeCompare(b.position) ||
  120. a.weeks.join(',').localeCompare(b.weeks.join(',')) ||
  121. a.startSection - b.startSection
  122. );
  123. let currentMergedCourse = null;
  124. for (const course of tempCourseList) {
  125. if (currentMergedCourse === null) {
  126. currentMergedCourse = { ...course };
  127. } else {
  128. const isSameCourseContext = currentMergedCourse.name === course.name &&
  129. currentMergedCourse.teacher === course.teacher &&
  130. currentMergedCourse.position === course.position &&
  131. currentMergedCourse.weeks.join(',') === course.weeks.join(',');
  132. const isConsecutive = course.day === currentMergedCourse.day &&
  133. course.startSection === currentMergedCourse.endSection + 1;
  134. if (isSameCourseContext && isConsecutive) {
  135. currentMergedCourse.endSection = course.endSection;
  136. } else {
  137. finalCourseList.push(currentMergedCourse);
  138. currentMergedCourse = { ...course };
  139. }
  140. }
  141. }
  142. if (currentMergedCourse !== null) {
  143. finalCourseList.push(currentMergedCourse);
  144. }
  145. console.log(`JS: transformSchedule 解析并合并完成,共 ${finalCourseList.length} 门课程。`);
  146. return finalCourseList;
  147. }
  148. function isLoginPage() {
  149. const url = window.location.href;
  150. return url.includes('jwgl.jyvtc.edu.cn/jyvtcjw/cas/login.action');
  151. }
  152. function validateYearInput(input) {
  153. console.log("JS: validateYearInput 被调用,输入: " + input);
  154. if (/^[0-9]{4}$/.test(input)) {
  155. console.log("JS: validateYearInput 验证通过。");
  156. return false;
  157. } else {
  158. console.log("JS: validateYearInput 验证失败。");
  159. return "请输入四位数字的学年!";
  160. }
  161. }
  162. async function promptUserToStart() {
  163. console.log("JS: 流程开始:显示公告。");
  164. return await window.AndroidBridgePromise.showAlert(
  165. "教务系统课表导入",
  166. "导入前请确保您已在浏览器中成功登录教务系统",
  167. "好的,开始导入"
  168. );
  169. }
  170. async function getAcademicYear() {
  171. const currentYear = new Date().getFullYear().toString();
  172. console.log("JS: 提示用户输入学年。");
  173. return await window.AndroidBridgePromise.showPrompt(
  174. "选择学年",
  175. "请输入要导入课程的学年(例如 2025):",
  176. currentYear,
  177. "validateYearInput"
  178. );
  179. }
  180. async function selectSemester() {
  181. const semesters = ["第一学期 (0)", "第二学期 (1)"];
  182. console.log("JS: 提示用户选择学期。");
  183. const semesterIndex = await window.AndroidBridgePromise.showSingleSelection(
  184. "选择学期",
  185. JSON.stringify(semesters),
  186. 0
  187. );
  188. return semesterIndex;
  189. }
  190. async function fetchAndParseCourses(academicYear, semesterIndex) {
  191. AndroidBridge.showToast("正在请求课表数据...");
  192. // 0是第一学期,1是第二学期
  193. const semesterCode = semesterIndex === 0 ? "0" : "1";
  194. const xnxqBody = `weeks=&xnxq=${academicYear}-${semesterCode}`;
  195. const url = "https://jwgl.jyvtc.edu.cn/jyvtcjw/frame/desk/showLessonScheduleDetail.action";
  196. console.log(`JS: 发送请求到 ${url}, body: ${xnxqBody}`);
  197. const requestOptions = {
  198. "headers": {
  199. "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  200. },
  201. "body": xnxqBody,
  202. "method": "POST",
  203. "credentials": "include"
  204. };
  205. try {
  206. const response = await fetch(url, requestOptions);
  207. if (!response.ok) {
  208. throw new Error(`网络请求失败。状态码: ${response.status} (${response.statusText})`);
  209. }
  210. const htmlText = await response.text();
  211. const courses = transformSchedule(htmlText);
  212. if (courses.length === 0) {
  213. AndroidBridge.showToast("未找到任何课程数据,请检查所选学年学期是否正确。");
  214. return null;
  215. }
  216. console.log(`JS: 课程数据解析成功,共找到 ${courses.length} 门课程。`);
  217. return { courses: courses };
  218. } catch (error) {
  219. AndroidBridge.showToast(`请求或解析失败: ${error.message}`);
  220. console.error('JS: Fetch/Parse Error:', error);
  221. return null;
  222. }
  223. }
  224. async function saveCourses(parsedCourses) {
  225. AndroidBridge.showToast(`正在保存 ${parsedCourses.length} 门课程...`);
  226. console.log(`JS: 尝试保存 ${parsedCourses.length} 门课程...`);
  227. try {
  228. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses, null, 2));
  229. console.log("JS: 课程保存成功!");
  230. return true;
  231. } catch (error) {
  232. AndroidBridge.showToast(`课程保存失败: ${error.message}`);
  233. console.error('JS: Save Courses Error:', error);
  234. return false;
  235. }
  236. }
  237. const Non_summerTimeSlots = [
  238. { number: 1, startTime: "08:00", endTime: "08:45" },
  239. { number: 2, startTime: "08:55", endTime: "09:40" },
  240. { number: 3, startTime: "10:10", endTime: "10:55" },
  241. { number: 4, startTime: "11:05", endTime: "11:50" },
  242. { number: 5, startTime: "14:30", endTime: "15:15" },
  243. { number: 6, startTime: "15:25", endTime: "16:10" },
  244. { number: 7, startTime: "16:30", endTime: "17:14" },
  245. { number: 8, startTime: "17:15", "endTime": "18:00" },
  246. { number: 9, startTime: "19:30", "endTime": "20:14" },
  247. { number: 10, startTime: "20:15", "endTime": "21:00" }
  248. ];
  249. const SummerTimeSlots = [
  250. { number: 1, startTime: "08:00", endTime: "08:45" },
  251. { number: 2, startTime: "08:55", endTime: "09:40" },
  252. { number: 3, startTime: "10:10", endTime: "10:55" },
  253. { number: 4, startTime: "11:05", endTime: "11:50" },
  254. { number: 5, startTime: "15:00", endTime: "15:45" },
  255. { number: 6, startTime: "15:55", endTime: "16:40" },
  256. { number: 7, startTime: "17:00", endTime: "17:44" },
  257. { number: 8, startTime: "17:45", "endTime": "18:30" },
  258. { number: 9, startTime: "19:30", "endTime": "20:14" },
  259. { number: 10, startTime: "20:15", "endTime": "21:00" }
  260. ];
  261. async function selectTimeSlotsType() {
  262. const timeSlotsOptions = ["非夏季作息", "夏季作息"];
  263. console.log("JS: 提示用户选择作息时间类型。");
  264. const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
  265. "选择作息时间",
  266. JSON.stringify(timeSlotsOptions),
  267. 0
  268. );
  269. return selectedIndex;
  270. }
  271. async function importPresetTimeSlots(timeSlots) {
  272. console.log(`JS: 准备导入 ${timeSlots.length} 个预设时间段。`);
  273. if (timeSlots.length > 0) {
  274. AndroidBridge.showToast(`正在导入 ${timeSlots.length} 个预设时间段...`);
  275. try {
  276. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  277. AndroidBridge.showToast("预设时间段导入成功!");
  278. console.log("JS: 预设时间段导入成功。");
  279. } catch (error) {
  280. AndroidBridge.showToast("导入时间段失败: " + error.message);
  281. console.error('JS: Save Time Slots Error:', error);
  282. }
  283. } else {
  284. AndroidBridge.showToast("警告:时间段为空,未导入时间段信息。");
  285. console.warn("JS: 警告:传入时间段为空,未导入时间段信息。");
  286. }
  287. }
  288. async function runImportFlow() {
  289. if (isLoginPage()) {
  290. AndroidBridge.showToast("导入失败:请先登录教务系统!");
  291. console.log("JS: 检测到当前在登录页面,终止导入。");
  292. return;
  293. }
  294. // 1. 公告和前置检查。
  295. const alertConfirmed = await promptUserToStart();
  296. if (!alertConfirmed) {
  297. AndroidBridge.showToast("用户取消了导入。");
  298. console.log("JS: 用户取消了导入流程。");
  299. return;
  300. }
  301. const academicYear = await getAcademicYear();
  302. if (academicYear === null) {
  303. AndroidBridge.showToast("导入已取消。");
  304. console.log("JS: 获取学年失败/取消,流程终止。");
  305. return;
  306. }
  307. console.log(`JS: 已选择学年: ${academicYear}`);
  308. const semesterIndex = await selectSemester();
  309. if (semesterIndex === null || semesterIndex === -1) {
  310. AndroidBridge.showToast("导入已取消。");
  311. console.log("JS: 选择学期失败/取消,流程终止。");
  312. return;
  313. }
  314. console.log(`JS: 已选择学期索引: ${semesterIndex}`);
  315. const result = await fetchAndParseCourses(academicYear, semesterIndex);
  316. if (result === null) {
  317. console.log("JS: 课程获取或解析失败,流程终止。");
  318. return;
  319. }
  320. const { courses } = result;
  321. // 5. 课程数据保存。
  322. const saveResult = await saveCourses(courses);
  323. if (!saveResult) {
  324. console.log("JS: 课程保存失败,流程终止。");
  325. return;
  326. }
  327. const timeSlotsIndex = await selectTimeSlotsType();
  328. let selectedTimeSlots = [];
  329. if (timeSlotsIndex === 0) {
  330. // 0: 非夏季作息
  331. selectedTimeSlots = Non_summerTimeSlots;
  332. console.log("JS: 已选择非夏季作息。");
  333. } else if (timeSlotsIndex === 1) {
  334. // 1: 夏季作息
  335. selectedTimeSlots = SummerTimeSlots;
  336. console.log("JS: 已选择夏季作息。");
  337. } else {
  338. selectedTimeSlots = Non_summerTimeSlots;
  339. console.warn("JS: 作息时间选择失败/取消,使用非夏季作息作为默认值。");
  340. }
  341. await importPresetTimeSlots(selectedTimeSlots);
  342. AndroidBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
  343. console.log("JS: 整个导入流程执行完毕并成功。");
  344. AndroidBridge.notifyTaskCompletion();
  345. }
  346. runImportFlow();