hnsf_02.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // ====================== 工具函数 ======================
  2. // 展开 weeks 字符串 -> 数字数组
  3. function parseWeeks(weeksStr) {
  4. const weeks = new Set();
  5. if (!weeksStr) return [];
  6. const parts = weeksStr.split(",");
  7. for (const part of parts) {
  8. if (part.includes("-")) {
  9. const [start, end] = part.split("-").map(n => parseInt(n));
  10. for (let i = start; i <= end && i <= 20; i++) {
  11. weeks.add(i);
  12. }
  13. } else {
  14. const n = parseInt(part);
  15. if (n >= 1 && n <= 20) weeks.add(n);
  16. }
  17. }
  18. return Array.from(weeks).sort((a, b) => a - b);
  19. }
  20. // 合并重复课程
  21. function mergeDuplicateCourses(courses) {
  22. const merged = [];
  23. const keyMap = {}; // key = name+day+startSection+endSection+position
  24. for (const c of courses) {
  25. const key = `${c.name}|${c.day}|${c.startSection}|${c.endSection}|${c.position}`;
  26. if (!keyMap[key]) {
  27. keyMap[key] = { ...c, weeks: [...c.weeks] };
  28. } else {
  29. keyMap[key].weeks = Array.from(new Set([...keyMap[key].weeks, ...c.weeks]));
  30. }
  31. }
  32. for (const k in keyMap) merged.push(keyMap[k]);
  33. return merged;
  34. }
  35. // ====================== 选择月份 ======================
  36. async function selectMonth() {
  37. try {
  38. // 生成月份选项
  39. const months = [];
  40. const currentYear = new Date().getFullYear();
  41. // 生成当前学年(9月到次年6月)的月份选项
  42. for (let month = 9; month <= 12; month++) {
  43. months.push(`${currentYear}年${month}月`);
  44. }
  45. for (let month = 1; month <= 6; month++) {
  46. months.push(`${currentYear + 1}年${month}月`);
  47. }
  48. const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
  49. "请选择要获取课程的月份",
  50. JSON.stringify(months),
  51. 0
  52. );
  53. if (selectedIndex === null) return null;
  54. const selectedMonth = months[selectedIndex];
  55. const year = parseInt(selectedMonth.split('年')[0]);
  56. const month = parseInt(selectedMonth.split('年')[1].split('月')[0]);
  57. // 计算该月的开始和结束日期
  58. const startDate = new Date(year, month - 1, 1);
  59. const endDate = new Date(year, month, 0); // 该月最后一天
  60. return {
  61. startDate: startDate,
  62. endDate: endDate,
  63. year: year,
  64. month: month
  65. };
  66. } catch (err) {
  67. console.error("选择月份失败:", err);
  68. return null;
  69. }
  70. }
  71. // ====================== 异步获取课程数据 ======================
  72. async function fetchCoursesData() {
  73. try {
  74. // 让用户选择月份
  75. const monthSelection = await selectMonth();
  76. if (!monthSelection) {
  77. AndroidBridge.showToast("用户取消选择月份!");
  78. return null;
  79. }
  80. // 格式化日期为API需要的格式
  81. const formatDate = (date) => {
  82. const year = date.getFullYear();
  83. const month = String(date.getMonth() + 1).padStart(2, '0');
  84. const day = String(date.getDate()).padStart(2, '0');
  85. return `${year}-${month}-${day}+00%3A00%3A00`;
  86. };
  87. const d1 = formatDate(monthSelection.startDate);
  88. const d2 = formatDate(monthSelection.endDate);
  89. const res = await fetch("https://jwc.htu.edu.cn/new/desktop/getCalendar", {
  90. method: 'POST',
  91. headers: {
  92. 'Content-Type': 'application/x-www-form-urlencoded',
  93. },
  94. body: `d1=${d1}&d2=${d2}`
  95. });
  96. const json = await res.json();
  97. return json; // 直接返回课程数据数组
  98. } catch (err) {
  99. console.error("获取课程数据失败:", err);
  100. AndroidBridge.showToast("获取课程数据失败:" + err.message);
  101. return null;
  102. }
  103. }
  104. // ====================== 处理新格式的课程数据 ======================
  105. async function processCoursesData(coursesData) {
  106. if (!coursesData || !Array.isArray(coursesData)) {
  107. return null;
  108. }
  109. const allCourses = [];
  110. coursesData.forEach((course) => {
  111. // 解析节次代码 (如 "0910" -> 第9节到第10节)
  112. const jcdm = course.jcdm || "";
  113. if (!jcdm || jcdm.length < 4) {
  114. return;
  115. }
  116. const startSection = parseInt(jcdm.substring(0, 2));
  117. const endSection = parseInt(jcdm.substring(2, 4));
  118. // 解析周次 (如 "10" -> [10])
  119. const weeks = parseWeeks(course.zc || "");
  120. if (!weeks.length) {
  121. return;
  122. }
  123. // 解析星期 (如 "3" -> 星期三)
  124. const day = parseInt(course.xq || "1");
  125. if (day < 1 || day > 7) {
  126. return;
  127. }
  128. // 处理地点格式,按照 [校本部]西校区新联楼0303 的格式
  129. let position = course.jxcdmc || "";
  130. const xqmc = course.xqmc || "校本部";
  131. if (position && !position.startsWith("[")) {
  132. position = `[${xqmc}]${position}`;
  133. }
  134. const processedCourse = {
  135. name: course.kcmc || "",
  136. teacher: course.teaxms || "",
  137. position: position,
  138. day: day,
  139. startSection: startSection,
  140. endSection: endSection,
  141. weeks: weeks
  142. };
  143. allCourses.push(processedCourse);
  144. });
  145. const mergedCourses = mergeDuplicateCourses(allCourses);
  146. return mergedCourses.length ? mergedCourses : null;
  147. }
  148. // ====================== 保存课程 ======================
  149. async function saveCourses(courses) {
  150. try {
  151. const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  152. if (result === true) {
  153. AndroidBridge.showToast("课程导入成功!");
  154. return true;
  155. } else {
  156. AndroidBridge.showToast("课程导入失败,请查看日志!");
  157. return false;
  158. }
  159. } catch (err) {
  160. console.error("保存课程失败:", err);
  161. AndroidBridge.showToast("保存课程失败:" + err.message);
  162. return false;
  163. }
  164. }
  165. // ====================== 导入预设时间段(一天5节大课,10节小课) ======================
  166. async function importPresetTimeSlots() {
  167. // 一天5节大课,10节小课的时间安排
  168. const presetTimeSlots = [
  169. { number: 1, startTime: "08:00", endTime: "08:45" }, // 第1节
  170. { number: 2, startTime: "08:45", endTime: "09:30" }, // 第2节
  171. { number: 3, startTime: "09:50", endTime: "10:35" }, // 第3节
  172. { number: 4, startTime: "10:35", endTime: "11:20" }, // 第4节
  173. { number: 5, startTime: "14:00", endTime: "14:45" }, // 第5节
  174. { number: 6, startTime: "14:45", endTime: "15:30" }, // 第6节
  175. { number: 7, startTime: "15:50", endTime: "16:35" }, // 第7节
  176. { number: 8, startTime: "16:35", endTime: "17:20" }, // 第8节
  177. { number: 9, startTime: "19:00", endTime: "19:45" }, // 第9节
  178. { number: 10, startTime: "19:45", endTime: "20:30" } // 第10节
  179. ];
  180. try {
  181. const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  182. if (result === true) {
  183. AndroidBridge.showToast("时间段导入成功!");
  184. } else {
  185. AndroidBridge.showToast("时间段导入失败,请查看日志!");
  186. }
  187. } catch (err) {
  188. console.error("时间段导入失败:", err);
  189. AndroidBridge.showToast("时间段导入失败:" + err.message);
  190. }
  191. }
  192. // ====================== 显示公告 ======================
  193. async function showAnnouncement() {
  194. try {
  195. const announcement = `
  196. 课程导入工具使用说明:
  197. 1. 本工具用于从教务系统获取课程数据
  198. 2. 请确保已登录教务系统
  199. 3. 选择要获取课程的月份
  200. 4. 工具会自动下载原始数据和处理后的数据
  201. 5. 支持一天10节课的时间安排
  202. 6. 自动合并重复课程
  203. 使用前请确认网络连接正常!
  204. `.trim();
  205. await window.AndroidBridgePromise.showAlert(
  206. "课程导入工具公告",
  207. announcement
  208. );
  209. } catch (err) {
  210. console.error("显示公告失败:", err);
  211. // 如果弹窗失败,使用Toast提示
  212. AndroidBridge.showToast("课程导入工具启动中...");
  213. }
  214. }
  215. // ====================== 主流程 ======================
  216. async function runImportFlow() {
  217. // 显示公告
  218. await showAnnouncement();
  219. AndroidBridge.showToast("课程导入流程即将开始...");
  220. // 1️⃣ 获取课程数据
  221. const coursesData = await fetchCoursesData();
  222. if (!coursesData) {
  223. AndroidBridge.notifyTaskCompletion();
  224. return;
  225. }
  226. // 2️⃣ 处理课程数据
  227. const courses = await processCoursesData(coursesData);
  228. if (!courses) {
  229. AndroidBridge.showToast("没有找到有效的课程数据!");
  230. AndroidBridge.notifyTaskCompletion();
  231. return;
  232. }
  233. // 3️⃣ 保存课程
  234. const saveResult = await saveCourses(courses);
  235. if (!saveResult) {
  236. AndroidBridge.notifyTaskCompletion();
  237. return;
  238. }
  239. // 4️⃣ 导入预设时间段
  240. await importPresetTimeSlots();
  241. AndroidBridge.showToast("所有任务完成!");
  242. AndroidBridge.notifyTaskCompletion();
  243. }
  244. // 启动流程
  245. runImportFlow();