dlutci.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // 大连工程学院教务系统(jw.dlutci.edu.cn)拾光课表导入适配脚本
  2. // 基于智慧教务(app)系统,接口为 xskcb.do 获取课表、xnxqcx.do 选择学期
  3. // 周次位图解析:SKZC 为"0"/"1"串,第 i 位为"1"表示第 i+1 周有课
  4. function parseWeeks(skzc) {
  5. const value = String(skzc || "");
  6. const weeks = [];
  7. for (let i = 0; i < value.length; i++) {
  8. if (value[i] === "1") weeks.push(i + 1);
  9. }
  10. return weeks;
  11. }
  12. function getErrorMessage(error) {
  13. if (error && typeof error.message === "string" && error.message.trim()) return error.message;
  14. if (typeof error === "string" && error.trim()) return error;
  15. try {
  16. const serialized = JSON.stringify(error);
  17. if (serialized && serialized !== "{}") return serialized;
  18. } catch (_) {
  19. // Ignore serialization failures.
  20. }
  21. return "未知错误";
  22. }
  23. // 教室:JASMC 为主,XXXQDM_DISPLAY 校区做后缀;均无则"待定"
  24. function parsePosition(row) {
  25. const campus = String(row.XXXQDM_DISPLAY || "").trim();
  26. const room = String(row.JASMC || "").trim();
  27. if (campus && room) return `${room}(${campus})`;
  28. return room || campus || "待定";
  29. }
  30. function parseCourse(row) {
  31. const day = Number(row.SKXQ);
  32. const startSection = Number(row.KSJC);
  33. const endSection = Number(row.JSJC);
  34. const weeks = parseWeeks(row.SKZC);
  35. if (!row.KCM || !day || day < 1 || day > 7 || !startSection || !endSection ||
  36. startSection > endSection || weeks.length === 0) return null;
  37. return {
  38. name: String(row.KCM).trim(),
  39. teacher: String(row.SKJS || "").split(/[\\/、,,]/)[0].trim() || "未知",
  40. position: parsePosition(row),
  41. day,
  42. startSection,
  43. endSection,
  44. weeks
  45. };
  46. }
  47. // 接口返回 JSON = { datas: { xskcb: { rows: [...] } } },按行为一门课;同一门课多处时段会分多条,按组合键去重
  48. function transformSchedule(payload) {
  49. const rows = payload && payload.datas && payload.datas.xskcb && payload.datas.xskcb.rows;
  50. if (!Array.isArray(rows)) return [];
  51. const rawCourses = rows.map(parseCourse).filter(Boolean);
  52. const seen = new Set();
  53. const courses = [];
  54. for (const c of rawCourses) {
  55. const key = `${c.name}|${c.day}|${c.startSection}|${c.endSection}|${c.weeks.join(',')}|${c.teacher}|${c.position}`;
  56. if (seen.has(key)) continue;
  57. seen.add(key);
  58. courses.push(c);
  59. }
  60. return courses;
  61. }
  62. // 判断是否登录页
  63. function isLoginPage() {
  64. const url = window.location.href;
  65. return /login/i.test(url);
  66. }
  67. async function fetchCurrentSemester() {
  68. const response = await fetch("/jwapp/sys/wdkb/modules/jshkcb/dqxnxq.do", {
  69. method: "POST",
  70. headers: {
  71. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  72. "X-Requested-With": "XMLHttpRequest"
  73. },
  74. body: "",
  75. credentials: "include"
  76. });
  77. if (!response.ok) throw new Error(`当前学期接口请求失败(HTTP ${response.status})`);
  78. const payload = await response.json();
  79. const rows = payload && payload.datas && payload.datas.dqxnxq && payload.datas.dqxnxq.rows;
  80. const term = Array.isArray(rows) && rows.length > 0 ? rows[0] : null;
  81. if (!term || (!term.DM && !term.XNXQDM)) throw new Error("未获取到当前学期信息。");
  82. return { code: String(term.XNXQDM || term.DM) };
  83. }
  84. async function fetchSemesterList() {
  85. const response = await fetch("/jwapp/sys/wdkb/modules/jshkcb/xnxqcx.do", {
  86. method: "POST",
  87. headers: {
  88. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  89. "X-Requested-With": "XMLHttpRequest"
  90. },
  91. body: "*order=-DM",
  92. credentials: "include"
  93. });
  94. if (!response.ok) throw new Error(`学期列表接口请求失败(HTTP ${response.status})`);
  95. const payload = await response.json();
  96. const rows = payload && payload.datas && payload.datas.xnxqcx && payload.datas.xnxqcx.rows;
  97. if (!Array.isArray(rows)) throw new Error("学期列表返回数据格式异常。");
  98. return rows.filter(item => item && (item.DM || item.XNXQDM)).map(item => {
  99. const code = String(item.XNXQDM || item.DM);
  100. const MC = String(item.MC || item.DM || item.XNXQDM);
  101. return { code, name: MC };
  102. });
  103. }
  104. // 选择学期,默认定位到当前学期
  105. async function selectSemester() {
  106. const current = await fetchCurrentSemester();
  107. const list = await fetchSemesterList();
  108. if (list.length === 0) throw new Error("未获取到学期列表。");
  109. const defaultIndex = Math.max(0, list.findIndex(s => s.code === current.code));
  110. const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  111. "请选择学期",
  112. JSON.stringify(list.map(s => s.name)),
  113. defaultIndex
  114. );
  115. if (selectedIndex === null || selectedIndex === undefined) return null;
  116. return list[selectedIndex];
  117. }
  118. async function fetchCourses(xnxqdm) {
  119. window.shiguangBridge.showToast("正在请求课表数据...");
  120. // 请求参数仅传 XNXQDM,即可返回该学期全部课表
  121. const response = await fetch("/jwapp/sys/wdkb/modules/xskcb/xskcb.do", {
  122. method: "POST",
  123. headers: {
  124. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  125. "X-Requested-With": "XMLHttpRequest"
  126. },
  127. body: `XNXQDM=${encodeURIComponent(xnxqdm)}`,
  128. credentials: "include"
  129. });
  130. if (!response.ok) throw new Error(`课表接口请求失败(HTTP ${response.status})`);
  131. const payload = await response.json();
  132. return transformSchedule(payload);
  133. }
  134. async function runImportFlow() {
  135. if (isLoginPage()) {
  136. window.shiguangBridge.showToast("导入失败:请先登录教务系统!");
  137. return;
  138. }
  139. const alertConfirmed = await window.shiguangBridgePromise.showAlert(
  140. "教务系统课表导入",
  141. "导入前请确保您已登录教务系统,建议在课表页面进行导入",
  142. "好的,开始导入"
  143. );
  144. if (!alertConfirmed) {
  145. window.shiguangBridge.showToast("用户取消了导入。");
  146. return;
  147. }
  148. try {
  149. const semester = await selectSemester();
  150. if (!semester) {
  151. window.shiguangBridge.showToast("用户取消了导入。");
  152. return;
  153. }
  154. console.log(`JS: 选择学期: ${semester.name} (${semester.code})`);
  155. const courses = await fetchCourses(semester.code);
  156. if (courses.length === 0) {
  157. window.shiguangBridge.showToast("未找到任何课程数据,请检查所选学期是否正确。");
  158. return;
  159. }
  160. window.shiguangBridge.showToast(`正在保存 ${courses.length} 门课程...`);
  161. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses, null, 2));
  162. window.shiguangBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
  163. window.shiguangBridge.notifyTaskCompletion();
  164. } catch (error) {
  165. window.shiguangBridge.showToast(`导入失败:${getErrorMessage(error)}`);
  166. console.error("JS: Import Error:", error);
  167. }
  168. }
  169. runImportFlow();