qut.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. // 青岛理工大学 - 正方教务系统 课程表适配脚本
  2. // 教务系统基地址:跟随当前登录页协议(http/https 的会话 cookie 是分开的,必须与登录页一致)
  3. var BASE_URL = window.location.origin;
  4. /**
  5. * 解析周次字符串,处理单双周和周次范围。
  6. */
  7. function parseWeeks(zcd) {
  8. if (!zcd) return [];
  9. var weekSets = String(zcd).replace(/,/g, ',').split(',');
  10. var weeks = [];
  11. for (var i = 0; i < weekSets.length; i++) {
  12. var trimmedSet = weekSets[i].trim();
  13. var rangeMatch = trimmedSet.match(/(\d+)\s*-\s*(\d+)\s*周?/);
  14. var singleMatch = trimmedSet.match(/^(\d+)\s*周?/);
  15. var start = 0;
  16. var end = 0;
  17. var processed = false;
  18. if (rangeMatch) {
  19. start = Number(rangeMatch[1]);
  20. end = Number(rangeMatch[2]);
  21. processed = true;
  22. } else if (singleMatch) {
  23. start = end = Number(singleMatch[1]);
  24. processed = true;
  25. }
  26. if (processed && start >= 1 && end >= start) {
  27. var isSingle = trimmedSet.indexOf('(单)') !== -1;
  28. var isDouble = trimmedSet.indexOf('(双)') !== -1;
  29. for (var w = start; w <= end; w++) {
  30. if (isSingle && w % 2 === 0) continue;
  31. if (isDouble && w % 2 !== 0) continue;
  32. weeks.push(w);
  33. }
  34. }
  35. }
  36. var uniqueWeeks = [];
  37. var seen = {};
  38. for (var j = 0; j < weeks.length; j++) {
  39. if (!seen[weeks[j]]) {
  40. seen[weeks[j]] = true;
  41. uniqueWeeks.push(weeks[j]);
  42. }
  43. }
  44. uniqueWeeks.sort(function(a, b) { return a - b; });
  45. return uniqueWeeks;
  46. }
  47. /**
  48. * 清洗课程名称中的特殊字符
  49. */
  50. function cleanCourseName(name) {
  51. return name.replace(/[★○●◇◆]/g, '').trim();
  52. }
  53. function mergeAndDistinctCourses(courses) {
  54. var sorted = courses.slice().sort(function(a, b) {
  55. return a.name.localeCompare(b.name) ||
  56. a.teacher.localeCompare(b.teacher) ||
  57. a.position.localeCompare(b.position) ||
  58. a.day - b.day ||
  59. a.weeks.join(',').localeCompare(b.weeks.join(',')) ||
  60. a.startSection - b.startSection;
  61. });
  62. var merged = [];
  63. sorted.forEach(function(course) {
  64. var previous = merged[merged.length - 1];
  65. var sameCourse = previous && previous.name === course.name &&
  66. previous.teacher === course.teacher && previous.position === course.position &&
  67. previous.day === course.day && previous.weeks.join(',') === course.weeks.join(',');
  68. if (sameCourse && previous.endSection + 1 >= course.startSection) {
  69. previous.endSection = Math.max(previous.endSection, course.endSection);
  70. } else if (sameCourse && previous.startSection === course.startSection && previous.endSection === course.endSection) {
  71. return;
  72. } else {
  73. merged.push(course);
  74. }
  75. });
  76. var sameSection = new Map();
  77. merged.forEach(function(course) {
  78. var key = [course.name, course.teacher, course.position, course.day,
  79. course.startSection, course.endSection].join('|');
  80. if (sameSection.has(key)) {
  81. var existing = sameSection.get(key);
  82. existing.weeks = Array.from(new Set(existing.weeks.concat(course.weeks))).sort(function(a, b) { return a - b; });
  83. } else {
  84. sameSection.set(key, course);
  85. }
  86. });
  87. return Array.from(sameSection.values()).sort(function(a, b) {
  88. return a.day - b.day || a.startSection - b.startSection || a.name.localeCompare(b.name);
  89. });
  90. }
  91. /**
  92. * 解析 API 返回的 JSON 数据。
  93. */
  94. function parseJsonData(jsonData) {
  95. console.log("JS: parseJsonData 正在解析 JSON 数据...");
  96. if (!jsonData || !Array.isArray(jsonData.kbList)) {
  97. console.warn("JS: JSON 数据结构错误或缺少 kbList 字段。");
  98. return [];
  99. }
  100. var rawCourseList = jsonData.kbList;
  101. var finalCourseList = [];
  102. for (var dbg = 0; dbg < Math.min(rawCourseList.length, 2); dbg++) {
  103. console.log("JS: 原始课程字段示例[" + dbg + "]: " + JSON.stringify(rawCourseList[dbg]));
  104. }
  105. for (var i = 0; i < rawCourseList.length; i++) {
  106. var rawCourse = rawCourseList[i];
  107. if (!rawCourse || typeof rawCourse !== "object" || !rawCourse.kcmc ||
  108. rawCourse.xqj == null || rawCourse.jcs == null || rawCourse.zcd == null) {
  109. continue;
  110. }
  111. var weeksArray = parseWeeks(rawCourse.zcd);
  112. if (weeksArray.length === 0) {
  113. continue;
  114. }
  115. var sectionMatch = String(rawCourse.jcs).match(/^\s*(?:第)?(\d+)\s*(?:-\s*(\d+))?\s*节?\s*$/);
  116. if (!sectionMatch) {
  117. console.warn("JS: 跳过无法解析节次的课程:" + rawCourse.kcmc + ",节次=" + rawCourse.jcs);
  118. continue;
  119. }
  120. var startSection = Number(sectionMatch[1]);
  121. var endSection = Number(sectionMatch[2] || sectionMatch[1]);
  122. var day = Number(rawCourse.xqj);
  123. if (!Number.isInteger(day) || !Number.isInteger(startSection) || !Number.isInteger(endSection) ||
  124. day < 1 || day > 7 || startSection < 1 || endSection < startSection) {
  125. continue;
  126. }
  127. finalCourseList.push({
  128. name: cleanCourseName(String(rawCourse.kcmc)),
  129. teacher: rawCourse.xm == null ? "" : String(rawCourse.xm).trim(),
  130. position: rawCourse.cdmc == null ? "" : String(rawCourse.cdmc).trim(),
  131. day: day,
  132. startSection: startSection,
  133. endSection: endSection,
  134. weeks: weeksArray
  135. });
  136. }
  137. finalCourseList = mergeAndDistinctCourses(finalCourseList);
  138. console.log("JS: JSON 数据解析完成,共找到 " + finalCourseList.length + " 门课程。");
  139. return finalCourseList;
  140. }
  141. /**
  142. * 构建课表配置,从课程数据中推断最大周次。
  143. */
  144. function buildCourseConfig(courses) {
  145. var maxWeek = 0;
  146. for (var i = 0; i < courses.length; i++) {
  147. var course = courses[i];
  148. for (var j = 0; j < course.weeks.length; j++) {
  149. if (course.weeks[j] > maxWeek) {
  150. maxWeek = course.weeks[j];
  151. }
  152. }
  153. }
  154. return {
  155. semesterStartDate: null,
  156. semesterTotalWeeks: maxWeek || 20,
  157. firstDayOfWeek: 1
  158. };
  159. }
  160. /**
  161. * 检查是否在登录页面。
  162. */
  163. function isLoginPage() {
  164. var url = window.location.href;
  165. return url.indexOf("/jwglxt/xtgl/login_slogin.html") !== -1;
  166. }
  167. async function promptUserToStart() {
  168. console.log("JS: 流程开始:显示公告。");
  169. return await window.shiguangBridgePromise.showAlert(
  170. "青岛理工大学课表导入",
  171. "导入前请确保您已在浏览器中成功登录青岛理工大学教务系统。\n脚本将通过正方教务接口读取课表。",
  172. "好的,开始导入"
  173. );
  174. }
  175. function parseSelectOptions(selectElement) {
  176. if (!selectElement) return { options: [], defaultIndex: 0 };
  177. var options = [];
  178. var defaultIndex = 0;
  179. Array.from(selectElement.querySelectorAll("option")).forEach(function(option) {
  180. var value = String(option.value || "").trim();
  181. if (!value) return;
  182. var text = String(option.textContent || "").trim() || value;
  183. if (option.selected) defaultIndex = options.length;
  184. options.push({ value: value, text: text });
  185. });
  186. return { options: options, defaultIndex: defaultIndex };
  187. }
  188. function parseTermOptions(doc) {
  189. var yearData = parseSelectOptions(doc.querySelector("#xnm"));
  190. var semesterData = parseSelectOptions(doc.querySelector("#xqm"));
  191. if (!yearData.options.length || !semesterData.options.length) {
  192. throw new Error("课表页面未找到有效的学年或学期选项");
  193. }
  194. return { yearData: yearData, semesterData: semesterData };
  195. }
  196. function isOnTimetablePage() {
  197. return window.location.pathname.indexOf("/jwglxt/kbcx/xskbcx_cxXskbcxIndex.html") !== -1;
  198. }
  199. function readCurrentTerm() {
  200. var yearElement = document.querySelector("#xnm");
  201. var semesterElement = document.querySelector("#xqm");
  202. var academicYear = yearElement ? String(yearElement.value || "").trim() : "";
  203. var semesterCode = semesterElement ? String(semesterElement.value || "").trim() : "";
  204. if (!academicYear || !semesterCode) {
  205. throw new Error("当前课表页面未读取到学年学期,请先在教务系统页面选择学年和学期");
  206. }
  207. return { academicYear: academicYear, semesterCode: semesterCode };
  208. }
  209. async function fetchTermPage() {
  210. var url = BASE_URL + "/jwglxt/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N253508&layout=default";
  211. var response = await fetch(url, { method: "GET", credentials: "include" });
  212. if (!response.ok) throw new Error("读取课表页面失败:HTTP " + response.status);
  213. return new DOMParser().parseFromString(await response.text(), "text/html");
  214. }
  215. async function selectTermFromPage(doc) {
  216. var termData = parseTermOptions(doc);
  217. var yearIndex = await window.shiguangBridgePromise.showSingleSelection(
  218. "选择学年", JSON.stringify(termData.yearData.options.map(function(item) { return item.text; })), termData.yearData.defaultIndex
  219. );
  220. if (yearIndex === null || yearIndex === -1 || !termData.yearData.options[yearIndex]) throw new Error("已取消或无法识别学年选择");
  221. var semesterIndex = await window.shiguangBridgePromise.showSingleSelection(
  222. "选择学期", JSON.stringify(termData.semesterData.options.map(function(item) { return item.text; })), termData.semesterData.defaultIndex
  223. );
  224. if (semesterIndex === null || semesterIndex === -1 || !termData.semesterData.options[semesterIndex]) throw new Error("已取消或无法识别学期选择");
  225. return {
  226. academicYear: termData.yearData.options[yearIndex].value,
  227. semesterCode: termData.semesterData.options[semesterIndex].value
  228. };
  229. }
  230. async function resolveTerm() {
  231. if (isOnTimetablePage()) return readCurrentTerm();
  232. return await selectTermFromPage(await fetchTermPage());
  233. }
  234. function normalizeStartDate(value) {
  235. var match = String(value || "").match(/(\d{4})[-\/.年](\d{1,2})[-\/.月](\d{1,2})/);
  236. if (!match) return null;
  237. return match[1] + "-" + match[2].padStart(2, "0") + "-" + match[3].padStart(2, "0");
  238. }
  239. function findSemesterStartDate(value) {
  240. if (value == null) return null;
  241. if (typeof value !== "object") return normalizeStartDate(value);
  242. if (Array.isArray(value)) {
  243. var firstWeek = value.find(function(item) {
  244. return item && (String(item.zs) === "1" || String(item.zsmc) === "1");
  245. }) || value[0];
  246. var firstWeekDate = findSemesterStartDate(firstWeek);
  247. if (firstWeekDate) return firstWeekDate;
  248. for (var i = 0; i < value.length; i++) {
  249. var date = findSemesterStartDate(value[i]);
  250. if (date) return date;
  251. }
  252. return null;
  253. }
  254. var fields = ["zrq", "zcrq", "rq", "ksrq"];
  255. for (var j = 0; j < fields.length; j++) {
  256. var fieldDate = normalizeStartDate(value[fields[j]]);
  257. if (fieldDate) return fieldDate;
  258. }
  259. var values = Object.values(value);
  260. for (var k = 0; k < values.length; k++) {
  261. var nestedDate = findSemesterStartDate(values[k]);
  262. if (nestedDate) return nestedDate;
  263. }
  264. return null;
  265. }
  266. async function fetchSemesterStartDate(academicYear, semesterCode) {
  267. var url = BASE_URL + "/jwglxt/kbcx/xskbcxZccx_cxZcByXnxq.html?gnmkdm=N2154";
  268. var requestBody = "xnm=" + encodeURIComponent(academicYear) + "&xqm=" + encodeURIComponent(semesterCode);
  269. try {
  270. var response = await fetch(url, {
  271. method: "POST",
  272. headers: {
  273. "accept": "application/json, text/javascript, */*; q=0.01",
  274. "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
  275. "x-requested-with": "XMLHttpRequest"
  276. },
  277. body: requestBody,
  278. credentials: "include"
  279. });
  280. if (!response.ok) {
  281. console.warn("JS: 开学日期接口请求失败:HTTP " + response.status);
  282. return null;
  283. }
  284. var responseText = await response.text();
  285. var data;
  286. try {
  287. data = JSON.parse(responseText);
  288. } catch (error) {
  289. console.warn("JS: 开学日期接口未返回 JSON,可能登录已过期。", error);
  290. return null;
  291. }
  292. var startDate = findSemesterStartDate(data);
  293. if (!startDate) console.warn("JS: 校历响应中未找到第 1 周开学日期。");
  294. else console.log("JS: 获取到开学日期:" + startDate);
  295. return startDate;
  296. } catch (error) {
  297. console.warn("JS: 获取学期开学日期失败:", error);
  298. return null;
  299. }
  300. }
  301. /**
  302. * 请求和解析课程数据。
  303. */
  304. async function fetchAndParseCourses(academicYear, semesterCode) {
  305. window.shiguangBridge.showToast("正在获取课表数据...");
  306. var requestBody = "xnm=" + encodeURIComponent(academicYear) +
  307. "&xqm=" + encodeURIComponent(semesterCode) +
  308. "&kzlx=ck&xsdm=&kclbdm=&kclxdm=";
  309. var url = BASE_URL + "/jwglxt/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N253508";
  310. console.log("JS: 发送请求到 " + url + ", body: " + requestBody);
  311. var requestOptions = {
  312. "headers": {
  313. "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
  314. "X-Requested-With": "XMLHttpRequest",
  315. },
  316. "body": requestBody,
  317. "method": "POST",
  318. "credentials": "include"
  319. };
  320. try {
  321. var requests = await Promise.all([fetch(url, requestOptions), fetchSemesterStartDate(academicYear, semesterCode)]);
  322. var response = requests[0];
  323. var semesterStartDate = requests[1];
  324. var jsonText = await response.text();
  325. var responseType = response.headers && response.headers.get
  326. ? response.headers.get("content-type") || "未知"
  327. : "未知";
  328. var responsePreview = jsonText.replace(/\s+/g, " ").trim().slice(0, 200) || "<空响应>";
  329. if (!response.ok) {
  330. var sessionHint = response.status === 901
  331. ? ";QUT 教务系统通常用 901 表示登录会话无效或已过期,请重新登录后停留在教务系统页面再测试"
  332. : "";
  333. throw new Error(
  334. "QUT 课表接口请求失败:HTTP " + response.status +
  335. (response.statusText ? " " + response.statusText : "") +
  336. sessionHint +
  337. ";响应类型=" + responseType +
  338. ";最终地址=" + (response.url || url) +
  339. ";响应摘要=" + responsePreview
  340. );
  341. }
  342. if (jsonText.indexOf("登录") !== -1 && jsonText.indexOf("密码") !== -1) {
  343. throw new Error(
  344. "QUT 课表接口返回了登录页面,当前登录会话已失效。" +
  345. "请重新登录教务系统后再导入;最终地址=" + (response.url || url) +
  346. ";响应类型=" + responseType
  347. );
  348. }
  349. var jsonData;
  350. try {
  351. jsonData = JSON.parse(jsonText);
  352. } catch (e) {
  353. throw new Error(
  354. "QUT 课表接口未返回有效 JSON:" + e.message +
  355. ";响应类型=" + responseType +
  356. ";最终地址=" + (response.url || url) +
  357. ";响应摘要=" + responsePreview
  358. );
  359. }
  360. var courses = parseJsonData(jsonData);
  361. if (courses.length === 0) {
  362. window.shiguangBridge.showToast("未找到任何课程数据,请检查所选学年学期是否正确或本学期无课。");
  363. return null;
  364. }
  365. console.log("JS: 课程数据解析成功,共找到 " + courses.length + " 门课程。");
  366. var config = buildCourseConfig(courses);
  367. config.semesterStartDate = semesterStartDate;
  368. return { courses: courses, config: config };
  369. } catch (error) {
  370. window.shiguangBridge.showToast("请求或解析失败: " + error.message);
  371. console.error('JS: Fetch/Parse Error:', error);
  372. return null;
  373. }
  374. }
  375. /**
  376. * 从正方系统获取该学期真实的节次作息时间。
  377. */
  378. async function fetchAndParseTimeSlots(academicYear, semesterCode) {
  379. var baseUrl = BASE_URL + "/jwglxt/kbcx/xskbcx_cxRjc.html";
  380. var requestBody = "xnm=" + encodeURIComponent(academicYear) +
  381. "&xqm=" + encodeURIComponent(semesterCode) +
  382. "&kzlx=ck&xsdm=&kclbdm=&kclxdm=";
  383. var gnmkdms = ["N2151", "N253508"];
  384. var lastError = null;
  385. for (var i = 0; i < gnmkdms.length; i++) {
  386. var url = baseUrl + "?gnmkdm=" + gnmkdms[i];
  387. try {
  388. var response = await fetch(url, {
  389. "headers": {
  390. "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
  391. "X-Requested-With": "XMLHttpRequest",
  392. },
  393. "body": requestBody,
  394. "method": "POST",
  395. "credentials": "include"
  396. });
  397. if (!response.ok) {
  398. lastError = "HTTP " + response.status;
  399. console.warn("JS: 节次时间接口请求失败(gnmkdm=" + gnmkdms[i] + "):HTTP " + response.status);
  400. continue;
  401. }
  402. var responseText = await response.text();
  403. var responsePreview = responseText.replace(/\s+/g, " ").trim().slice(0, 200) || "<空响应>";
  404. var data;
  405. try {
  406. data = JSON.parse(responseText);
  407. } catch (e) {
  408. lastError = "非 JSON:响应=" + responsePreview;
  409. console.warn("JS: 节次时间接口未返回 JSON(gnmkdm=" + gnmkdms[i] + "):", e, "响应摘要=" + responsePreview);
  410. continue;
  411. }
  412. if (data && !Array.isArray(data) && Array.isArray(data.data)) {
  413. data = data.data;
  414. }
  415. if (!Array.isArray(data)) {
  416. lastError = "格式异常:响应=" + responsePreview;
  417. console.warn("JS: 节次时间接口返回格式异常(gnmkdm=" + gnmkdms[i] + "):响应摘要=" + responsePreview);
  418. continue;
  419. }
  420. var timeSlots = [];
  421. for (var j = 0; j < data.length; j++) {
  422. var item = data[j];
  423. if (!item || typeof item !== "object") continue;
  424. var number = Number(item.jcdm != null ? item.jcdm : item.jcmc);
  425. var startTime = "";
  426. var endTime = "";
  427. if (item.qssj != null && item.jssj != null) {
  428. startTime = String(item.qssj).trim();
  429. endTime = String(item.jssj).trim();
  430. } else if (item.sksj) {
  431. var sksjParts = String(item.sksj).split(/[~\-—至到]/);
  432. if (sksjParts.length >= 2) {
  433. startTime = sksjParts[0].trim();
  434. endTime = sksjParts[1].trim();
  435. }
  436. }
  437. startTime = startTime.slice(0, 5);
  438. endTime = endTime.slice(0, 5);
  439. if (!(number > 0) || !startTime || !endTime) continue;
  440. timeSlots.push({ number: number, startTime: startTime, endTime: endTime });
  441. }
  442. timeSlots.sort(function(a, b) { return a.number - b.number; });
  443. if (timeSlots.length === 0) {
  444. lastError = "无有效数据:响应=" + responsePreview;
  445. console.warn("JS: 节次时间接口未返回有效作息时间(gnmkdm=" + gnmkdms[i] + "):响应摘要=" + responsePreview);
  446. continue;
  447. }
  448. console.log("JS: 获取到真实节次作息 " + timeSlots.length + " 条(gnmkdm=" + gnmkdms[i] + "),第1节 " +
  449. timeSlots[0].startTime + "-" + timeSlots[0].endTime);
  450. return timeSlots;
  451. } catch (error) {
  452. lastError = error.message || String(error);
  453. console.warn("JS: 获取节次作息时间失败(gnmkdm=" + gnmkdms[i] + "):", error);
  454. }
  455. }
  456. console.warn("JS: 所有节次时间接口尝试均失败,最后原因:" + lastError);
  457. return null;
  458. }
  459. async function saveCourses(parsedCourses) {
  460. window.shiguangBridge.showToast("正在保存 " + parsedCourses.length + " 门课程...");
  461. console.log("JS: 尝试保存 " + parsedCourses.length + " 门课程...");
  462. try {
  463. await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
  464. console.log("JS: 课程保存成功!");
  465. return true;
  466. } catch (error) {
  467. window.shiguangBridge.showToast("课程保存失败: " + error.message);
  468. console.error('JS: Save Courses Error:', error);
  469. return false;
  470. }
  471. }
  472. // 青岛理工大学统一作息时间(兜底,接口获取失败时使用)
  473. var TimeSlots = [
  474. { number: 1, startTime: "08:00", endTime: "08:45" },
  475. { number: 2, startTime: "08:50", endTime: "09:35" },
  476. { number: 3, startTime: "09:55", endTime: "10:40" },
  477. { number: 4, startTime: "10:45", endTime: "11:30" },
  478. { number: 5, startTime: "11:35", endTime: "12:20" },
  479. { number: 6, startTime: "14:00", endTime: "14:45" },
  480. { number: 7, startTime: "14:50", endTime: "15:35" },
  481. { number: 8, startTime: "15:55", endTime: "16:40" },
  482. { number: 9, startTime: "16:45", endTime: "17:30" },
  483. { number: 10, startTime: "19:00", endTime: "19:45" }
  484. ];
  485. async function importPresetTimeSlots(timeSlots) {
  486. console.log("JS: 准备导入 " + timeSlots.length + " 个预设时间段。");
  487. if (timeSlots.length > 0) {
  488. window.shiguangBridge.showToast("正在导入 " + timeSlots.length + " 个预设时间段...");
  489. try {
  490. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  491. window.shiguangBridge.showToast("预设时间段导入成功!");
  492. console.log("JS: 预设时间段导入成功。");
  493. } catch (error) {
  494. window.shiguangBridge.showToast("导入时间段失败: " + error.message);
  495. console.error('JS: Save Time Slots Error:', error);
  496. }
  497. } else {
  498. window.shiguangBridge.showToast("警告:时间段为空,未导入时间段信息。");
  499. console.warn("JS: 警告:传入时间段为空,未导入时间段信息。");
  500. }
  501. }
  502. async function runImportFlow() {
  503. try {
  504. if (isLoginPage()) {
  505. throw new Error("当前是登录页面,请先登录青岛理工大学教务系统");
  506. }
  507. window.shiguangBridge.showToast("拾光课程表 - 青岛理工大学适配");
  508. var alertConfirmed = await promptUserToStart();
  509. if (!alertConfirmed) {
  510. window.shiguangBridge.showToast("用户取消了导入。");
  511. console.log("JS: 用户取消了导入流程。");
  512. return;
  513. }
  514. var term = await resolveTerm();
  515. console.log("JS: 已选择学年学期: " + term.academicYear + ", " + term.semesterCode);
  516. var result = await fetchAndParseCourses(term.academicYear, term.semesterCode);
  517. if (result === null) {
  518. console.log("JS: 课程获取或解析失败,流程终止。");
  519. return;
  520. }
  521. var courses = result.courses;
  522. var config = result.config;
  523. var saveResult = await saveCourses(courses);
  524. if (!saveResult) {
  525. console.log("JS: 课程保存失败,流程终止。");
  526. return;
  527. }
  528. var timeSlots = await fetchAndParseTimeSlots(term.academicYear, term.semesterCode);
  529. if (timeSlots === null) {
  530. timeSlots = TimeSlots;
  531. window.shiguangBridge.showToast("未获取到学校作息时间,已使用内置统一作息作为兜底。");
  532. }
  533. if (timeSlots.length > 0) {
  534. var firstSlotDuration = Math.round(
  535. (timeSlots[0].endTime.slice(0, 2) * 60 + Number(timeSlots[0].endTime.slice(3, 5))) -
  536. (timeSlots[0].startTime.slice(0, 2) * 60 + Number(timeSlots[0].startTime.slice(3, 5)))
  537. );
  538. if (firstSlotDuration >= 20) {
  539. config.defaultClassDuration = Math.round(firstSlotDuration / 5) * 5;
  540. config.defaultBreakDuration = 10;
  541. }
  542. }
  543. try {
  544. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
  545. window.shiguangBridge.showToast("课表配置更新成功!总周数:" + config.semesterTotalWeeks + "周。");
  546. } catch (error) {
  547. window.shiguangBridge.showToast("课表配置保存失败: " + error.message);
  548. console.error('JS: Save Config Error:', error);
  549. }
  550. await importPresetTimeSlots(timeSlots);
  551. window.shiguangBridge.showToast("成功导入 " + courses.length + " 门课程!");
  552. console.log("JS: 整个导入流程执行完毕并成功。");
  553. window.shiguangBridge.notifyTaskCompletion();
  554. } catch (error) {
  555. window.shiguangBridge.showToast("导入失败: " + error.message);
  556. console.error("JS: 导入流程失败:", error);
  557. }
  558. }
  559. runImportFlow();