sxdzkj.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // 山西电子科技学院(sxdzkj.edu.cn) 拾光课程表适配脚本
  2. // 基于青果教务系统适配
  3. // 适配器ID: SXDZKJ
  4. async function checkLoginEnvironment() {
  5. const currentUrl = window.location.href;
  6. if (currentUrl.includes("sso.sxdzkj.edu.cn/sso-auth") || currentUrl.includes("cas/login")) {
  7. window.shiguangBridge.showToast("请先登录教务系统再进行导入");
  8. return false;
  9. }
  10. return true;
  11. }
  12. function parseWeeks(weekStr) {
  13. const weeks = [];
  14. const groups = weekStr.split(',');
  15. groups.forEach(group => {
  16. const isSingle = group.includes('单');
  17. const isDouble = group.includes('双');
  18. const rangeMatch = group.match(/(\d+)-(\d+)/);
  19. if (rangeMatch) {
  20. const start = parseInt(rangeMatch[1]);
  21. const end = parseInt(rangeMatch[2]);
  22. for (let i = start; i <= end; i++) {
  23. if (isSingle && i % 2 === 0) continue;
  24. if (isDouble && i % 2 !== 0) continue;
  25. weeks.push(i);
  26. }
  27. } else {
  28. const num = parseInt(group.replace(/[^\d]/g, ''));
  29. if (!isNaN(num)) weeks.push(num);
  30. }
  31. });
  32. return Array.from(new Set(weeks)).sort((a, b) => a - b);
  33. }
  34. function encodeParams(params) {
  35. return btoa(params);
  36. }
  37. const dayMap = {
  38. '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '日': 7, '天': 7
  39. };
  40. function parseScheduleString(scheduleStr, teacher, courseName) {
  41. const results = [];
  42. if (!scheduleStr || scheduleStr.trim() === '') return results;
  43. const sessions = scheduleStr.split(';').map(s => s.trim()).filter(s => s);
  44. sessions.forEach(session => {
  45. // 有地点: "1-16周 三[7-8] 南-317(115)" / "1-16周(单) 一[5-6] 南-311(115)"
  46. // 无地点: "1-16周 三[5-6]"
  47. const match = session.match(/([\d\-]+周(?:\([单双]\))?)\s+([一二三四五六日天])\[(\d+\-\d+)\](?:\s*(.*))?/);
  48. if (match) {
  49. const weekStr = match[1];
  50. const dayChar = match[2];
  51. const sectionStr = match[3];
  52. const position = (match[4] || "").trim();
  53. const weeks = parseWeeks(weekStr);
  54. const day = dayMap[dayChar];
  55. const sections = sectionStr.split('-').map(Number);
  56. if (weeks.length > 0 && day && sections.length === 2) {
  57. results.push({
  58. name: courseName,
  59. teacher: teacher,
  60. position: position,
  61. day: day,
  62. startSection: sections[0],
  63. endSection: sections[1],
  64. weeks: weeks
  65. });
  66. }
  67. }
  68. });
  69. return results;
  70. }
  71. function parseCourseData(htmlText) {
  72. const parser = new DOMParser();
  73. const doc = parser.parseFromString(htmlText, 'text/html');
  74. const allCourses = [];
  75. const allTables = doc.querySelectorAll('table');
  76. if (allTables.length === 0) {
  77. console.warn("未找到表格");
  78. return [];
  79. }
  80. // 课程列表(含上课时间地点列)
  81. allTables.forEach(table => {
  82. if (!/上课时间地点/.test(table.textContent)) return;
  83. allCourses.push(...parseCourseList(table));
  84. });
  85. console.log("解析完成,共课程条目:", allCourses.length);
  86. return allCourses;
  87. }
  88. function parseCourseList(table) {
  89. const courses = [];
  90. const rows = table.querySelectorAll('tr');
  91. rows.forEach(row => {
  92. const cells = row.querySelectorAll('td');
  93. // 课程列表:第3列课程、第7列教师、第11列时间地点
  94. if (cells.length < 11) return;
  95. const courseCell = cells[2];
  96. const teacherCell = cells[6];
  97. const scheduleCell = cells[10];
  98. const courseName = courseCell.textContent.trim().replace(/\[.*?\]/g, '');
  99. const teacher = teacherCell.textContent.trim().replace(/\[.*?\]/g, '');
  100. const scheduleStr = scheduleCell.textContent.trim();
  101. if (!scheduleStr) return;
  102. const parsed = parseScheduleString(scheduleStr, teacher, courseName);
  103. courses.push(...parsed);
  104. });
  105. return courses;
  106. }
  107. async function getYearAndSemester() {
  108. const currentYear = new Date().getFullYear();
  109. const yearStr = await window.shiguangBridgePromise.showPrompt(
  110. "输入学年",
  111. "请输入学年(如 2025-2026 输入2025):",
  112. currentYear.toString(),
  113. "validateYearInput"
  114. );
  115. if (yearStr === null) return null;
  116. const semesterIndex = await window.shiguangBridgePromise.showSingleSelection(
  117. "选择学期",
  118. JSON.stringify(["第一学期", "第二学期"]),
  119. 0
  120. );
  121. if (semesterIndex === null) return null;
  122. return {
  123. xn: yearStr,
  124. xq: semesterIndex === 0 ? "0" : "1"
  125. };
  126. }
  127. function validateYearInput(input) {
  128. if (/^[0-9]{4}$/.test(input)) {
  129. return false;
  130. } else {
  131. return "请输入四位数字的学年!";
  132. }
  133. }
  134. async function fetchCourses(xn, xq) {
  135. try {
  136. window.shiguangBridge.showToast("正在获取课表数据...");
  137. let xh = "";
  138. try {
  139. const userCodeMatch = document.cookie.match(/userCode[=:]([^;]+)/);
  140. if (userCodeMatch) xh = userCodeMatch[1];
  141. } catch (e) {}
  142. if (!xh) {
  143. xh = await window.shiguangBridgePromise.showPrompt(
  144. "输入学号",
  145. "请输入你的学号:",
  146. "",
  147. null
  148. );
  149. if (!xh) return [];
  150. }
  151. console.log("使用参数:", { xn, xq, xh });
  152. const paramStr = `xn=${xn}&xq=${xq}&xh=${xh}`;
  153. const encodedParams = encodeParams(paramStr);
  154. const url = `https://jwxt.sxdzkj.edu.cn/wsxk/xkjg.ckdgxsxdkchj_data10319.jsp?params=${encodedParams}`;
  155. console.log("请求课表数据:", url);
  156. const response = await fetch(url, {
  157. method: "GET",
  158. credentials: "include"
  159. });
  160. if (!response.ok) {
  161. throw new Error(`请求失败: ${response.status}`);
  162. }
  163. const arrayBuffer = await response.arrayBuffer();
  164. let text;
  165. try {
  166. text = new TextDecoder('gbk').decode(arrayBuffer);
  167. } catch (e) {
  168. text = new TextDecoder('utf-8').decode(arrayBuffer);
  169. }
  170. console.log("课表响应长度:", text.length);
  171. return parseCourseData(text);
  172. } catch (error) {
  173. window.shiguangBridge.showToast("获取课表失败: " + error.message);
  174. console.error("获取课表失败:", error);
  175. return [];
  176. }
  177. }
  178. async function importPresetTimeSlots() {
  179. const slots = [
  180. { "number": 1, "startTime": "08:00", "endTime": "08:50" },
  181. { "number": 2, "startTime": "09:00", "endTime": "09:50" },
  182. { "number": 3, "startTime": "10:10", "endTime": "11:00" },
  183. { "number": 4, "startTime": "11:10", "endTime": "12:00" },
  184. { "number": 5, "startTime": "14:00", "endTime": "14:50" },
  185. { "number": 6, "startTime": "15:00", "endTime": "15:50" },
  186. { "number": 7, "startTime": "16:10", "endTime": "17:00" },
  187. { "number": 8, "startTime": "17:10", "endTime": "18:00" },
  188. { "number": 9, "startTime": "19:00", "endTime": "19:50" },
  189. { "number": 10, "startTime": "20:00", "endTime": "20:50" }
  190. ];
  191. try {
  192. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(slots));
  193. window.shiguangBridge.showToast("时间段导入成功");
  194. } catch (error) {
  195. console.error("导入时间段失败:", error);
  196. }
  197. }
  198. async function runImportFlow() {
  199. const isReady = await checkLoginEnvironment();
  200. if (!isReady) return;
  201. const confirmed = await window.shiguangBridgePromise.showAlert(
  202. "教务导入",
  203. "山西电子科技学院课表导入\n\n请确保已登录教务系统。\n点击确定开始导入。",
  204. "确定导入"
  205. );
  206. if (!confirmed) return;
  207. const semesterParams = await getYearAndSemester();
  208. if (!semesterParams) {
  209. window.shiguangBridge.showToast("导入已取消");
  210. return;
  211. }
  212. console.log("选择的学期:", semesterParams);
  213. const courses = await fetchCourses(semesterParams.xn, semesterParams.xq);
  214. if (!courses || courses.length === 0) {
  215. window.shiguangBridge.showToast("未找到课程数据");
  216. return;
  217. }
  218. console.log("解析到的课程:", courses);
  219. try {
  220. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  221. window.shiguangBridge.showToast(`成功保存 ${courses.length} 门课程`);
  222. } catch (error) {
  223. window.shiguangBridge.showToast("保存课程失败: " + error.message);
  224. return;
  225. }
  226. await importPresetTimeSlots();
  227. window.shiguangBridge.showToast(`导入完成!共 ${courses.length} 条课程记录`);
  228. window.shiguangBridge.notifyTaskCompletion();
  229. }
  230. runImportFlow();