ynufe.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. // ========== 工具函数 ==========
  2. /**
  3. * 解析周数字符串
  4. * @param {string} Str 如:1-6,7-13周(单)
  5. * @returns {Array} 返回数组 [1,3,5,7,9,11,13]
  6. */
  7. function getWeeks(Str) {
  8. function range(con, tag) {
  9. let retWeek = [];
  10. con.slice(0, -1).split(',').forEach(w => {
  11. let tt = w.split('-');
  12. let start = parseInt(tt[0]);
  13. let end = parseInt(tt[tt.length - 1]);
  14. if (tag === 1 || tag === 2) {
  15. retWeek.push(...Array(end + 1 - start).fill(start).map((x, y) => x + y).filter(f => {
  16. return f % tag === 0;
  17. }));
  18. } else {
  19. retWeek.push(...Array(end + 1 - start).fill(start).map((x, y) => x + y).filter(v => {
  20. return v % 2 !== 0;
  21. }));
  22. }
  23. });
  24. return retWeek;
  25. }
  26. Str = Str.replace(/[(){}|第\[\]]/g, "").replace(/到/g, "-");
  27. let reWeek = [];
  28. let week1 = [];
  29. while (Str.search(/周|\s/) !== -1) {
  30. let index = Str.search(/周|\s/);
  31. if (Str[index + 1] === '单' || Str[index + 1] === '双') {
  32. week1.push(Str.slice(0, index + 2).replace(/周|\s/g, ""));
  33. index += 2;
  34. } else {
  35. week1.push(Str.slice(0, index + 1).replace(/周|\s/g, ""));
  36. index += 1;
  37. }
  38. Str = Str.slice(index);
  39. index = Str.search(/\d/);
  40. if (index !== -1) Str = Str.slice(index);
  41. else Str = "";
  42. }
  43. if (Str.length !== 0) week1.push(Str);
  44. week1.forEach(v => {
  45. if (v.slice(-1) === "双") {
  46. reWeek.push(...range(v, 2));
  47. } else if (v.slice(-1) === "单") {
  48. reWeek.push(...range(v, 3));
  49. } else {
  50. reWeek.push(...range(v + "全", 1));
  51. }
  52. });
  53. return reWeek;
  54. }
  55. /**
  56. * 解析节次字符串
  57. * @param {string} Str 如: 1-4节 或 1-2-3-4节
  58. * @returns {Array} [1,2,3,4]
  59. */
  60. function getSection(Str) {
  61. let reJc = [];
  62. let strArr = Str.replace("节", "").trim().split("-");
  63. if (strArr.length <= 2) {
  64. for (let i = Number(strArr[0]); i <= Number(strArr[strArr.length - 1]); i++) {
  65. reJc.push(Number(i));
  66. }
  67. } else {
  68. strArr.forEach(v => {
  69. reJc.push(Number(v));
  70. });
  71. }
  72. return reJc;
  73. }
  74. /**
  75. * 检查是否在登录页面
  76. * @returns {boolean}
  77. */
  78. function isLoginPage() {
  79. const url = window.location.href;
  80. // 检查URL是否包含登录页面特征
  81. return url.includes('login') || url.includes('Login') ||
  82. document.querySelector('input[type="password"]') !== null;
  83. }
  84. /**
  85. * 解析课程HTML数据
  86. * @param {string} html 课程表HTML
  87. * @returns {Array} 课程数组
  88. */
  89. function parseScheduleHtml(html) {
  90. let result = [];
  91. let uniqueCourses = []; // 移到外部作用域
  92. try {
  93. // 创建临时div来解析HTML
  94. const tempDiv = document.createElement('div');
  95. tempDiv.innerHTML = html;
  96. console.log('开始解析课程表HTML...');
  97. // 查找课程表格
  98. const table = tempDiv.querySelector('#kbtable') || tempDiv.querySelector('table');
  99. if (!table) {
  100. throw new Error('未找到课程表格');
  101. }
  102. // 遍历所有行(每行是一个大节时间段)
  103. const rows = table.querySelectorAll('tr');
  104. console.log(`找到 ${rows.length} 行`);
  105. // 用于记录已处理的div,避免重复(跨大节课程会在多行出现)
  106. const processedDivs = new Set();
  107. rows.forEach((tr, rowIdx) => {
  108. const tds = tr.querySelectorAll('td');
  109. // 遍历这一行的所有td列
  110. // 注意: querySelectorAll('td')只选择td元素,不包括th
  111. // 所以td[0]就是星期一, td[1]是星期二, ..., td[6]是星期日
  112. tds.forEach((td, colIdx) => {
  113. // 查找这个单元格里的课程内容div
  114. const hiddenDiv = td.querySelector('div.kbcontent');
  115. // 如果没有隐藏div或内容为空,跳过
  116. if (!hiddenDiv) {
  117. return;
  118. }
  119. // 检查是否已经处理过这个div(根据name属性去重)
  120. const divName = hiddenDiv.getAttribute('name') || hiddenDiv.getAttribute('id');
  121. if (divName && processedDivs.has(divName)) {
  122. return; // 已处理过,跳过
  123. }
  124. const divText = hiddenDiv.textContent.trim();
  125. if (!divText || divText.length <= 6) {
  126. return;
  127. }
  128. // 从div的name属性提取星期信息
  129. // name格式: "hash-星期-序号" 例如 "EBC6F96389D143DC9C53084617F9C7D2-2-1"
  130. // 其中第二部分的数字: 1=星期一, 2=星期二, ..., 7=星期日
  131. let day = colIdx + 1; // 默认使用列索引
  132. if (divName) {
  133. const nameParts = divName.split('-');
  134. if (nameParts.length >= 3) {
  135. const dayFromName = parseInt(nameParts[1]);
  136. if (!isNaN(dayFromName) && dayFromName >= 1 && dayFromName <= 7) {
  137. day = dayFromName;
  138. }
  139. }
  140. }
  141. // 标记为已处理
  142. if (divName) {
  143. processedDivs.add(divName);
  144. }
  145. console.log(`\n[行${rowIdx} 列${colIdx} 星期${day}]`);
  146. console.log(`内容预览: ${divText.substring(0, 50)}...`);
  147. // 可能包含多个课程,用 ----- 分隔
  148. const courseSections = hiddenDiv.innerHTML.split(/-----+/);
  149. console.log(`分割成 ${courseSections.length} 个课程段`);
  150. // 用于课程段去重(避免完全相同的课程段被重复添加)
  151. const processedSections = new Set();
  152. // 遍历每个课程段
  153. courseSections.forEach((section, sectionIdx) => {
  154. const sectionText = section.replace(/<[^>]*>/g, '').trim();
  155. if (!sectionText || sectionText.length < 3) {
  156. return;
  157. }
  158. // 检查是否已经处理过完全相同的课程段(内容去重)
  159. if (processedSections.has(sectionText)) {
  160. console.log(` 跳过重复课程段 ${sectionIdx + 1}`);
  161. return;
  162. }
  163. processedSections.add(sectionText);
  164. console.log(` 课程段 ${sectionIdx + 1}:`);
  165. console.log(` 原始HTML:`, section.substring(0, 200));
  166. let course = {
  167. day: day, // 星期几(1=周一, 2=周二, ..., 7=周日)
  168. weeks: [],
  169. sections: [],
  170. name: '',
  171. teacher: '',
  172. position: ''
  173. };
  174. // 解析HTML,按br分割成行
  175. const lines = section.split(/<br\s*\/?>/i);
  176. console.log(` 分割成 ${lines.length} 行`);
  177. let firstTextLine = true; // 标记是否是第一个有效文本行
  178. lines.forEach((line, lineIdx) => {
  179. // 跳过空行
  180. const plainText = line.replace(/<[^>]*>/g, '').trim();
  181. if (!plainText || plainText === '&nbsp;') {
  182. return;
  183. }
  184. console.log(` 行${lineIdx}: ${line.substring(0, 100)}`);
  185. console.log(` 纯文本: ${plainText}`);
  186. // 第一个有效文本行就是课程名(没有title属性)
  187. if (firstTextLine && !course.name) {
  188. // 移除span标签(包含调课标记如&nbspO)但保留其他内容
  189. let courseName = line.replace(/<span[^>]*>.*?<\/span>/gi, '').trim();
  190. // 提取纯文本
  191. courseName = courseName.replace(/<[^>]*>/g, '').trim();
  192. // 清理HTML实体
  193. courseName = courseName.replace(/&nbsp;/g, ' ').trim();
  194. course.name = courseName;
  195. console.log(` ✓ 第一行作为课程名: ${course.name}`);
  196. firstTextLine = false;
  197. return;
  198. }
  199. firstTextLine = false;
  200. // 检查这一行的title属性(使用双引号)
  201. if (line.includes('title="老师"')) {
  202. course.teacher = plainText;
  203. console.log(` ✓ 匹配老师: ${course.teacher}`);
  204. }
  205. else if (line.includes('title="教室"')) {
  206. // 对于教室,需要先移除隐藏的font标签,再提取文本
  207. const cleanLine = line.replace(/<font[^>]*style="display:none;"[^>]*>.*?<\/font>/gi, '');
  208. const cleanText = cleanLine.replace(/<[^>]*>/g, '').trim();
  209. // 再移除可能残留的前导数字
  210. const finalPosition = cleanText.replace(/^[\d-]+/, '').trim();
  211. course.position = finalPosition;
  212. console.log(` ✓ 匹配教室: ${course.position}`);
  213. }
  214. else if (line.includes('title="周次(节次)"')) {
  215. console.log(` ✓ 匹配周次节次: ${plainText}`);
  216. // 解析周次: "1-18(周)[06-07节]"
  217. const weekMatch = plainText.match(/^(.+?)\(周\)/);
  218. if (weekMatch) {
  219. const weekStr = weekMatch[1];
  220. course.weeks = getWeeks(weekStr + '周');
  221. console.log(` -> 周: ${course.weeks}`);
  222. }
  223. // 解析节次: "[06-07节]"
  224. const sectionMatch = plainText.match(/\[(.+?)节?\]/);
  225. if (sectionMatch) {
  226. const sectionStr = sectionMatch[1];
  227. course.sections = getSection(sectionStr + '节');
  228. console.log(` -> 节: ${course.sections}`);
  229. }
  230. }
  231. // 如果没有找到教室,尝试从包含隐藏font的行提取
  232. // 这行可能格式如: <font style="display:none;">01-02</font><font style="display:none;">20</font>北院卓媒220
  233. else if (!course.position && line.includes('style="display:none;"')) {
  234. // 移除所有隐藏的font标签
  235. const visibleText = line.replace(/<font[^>]*style="display:none;"[^>]*>.*?<\/font>/gi, '')
  236. .replace(/<[^>]*>/g, '')
  237. .trim();
  238. if (visibleText && visibleText.length > 0) {
  239. // 移除所有前导的数字和连字符(如 "01-0220" 或 "06-0722")
  240. // 匹配模式:开头的数字-数字组合
  241. const cleanPosition = visibleText.replace(/^[\d-]+/, '').trim();
  242. if (cleanPosition.length > 0) {
  243. course.position = cleanPosition;
  244. console.log(` ✓ 提取教室(清理后): ${course.position}`);
  245. }
  246. }
  247. }
  248. });
  249. // 验证并添加课程
  250. if (course.name && course.weeks.length > 0 && course.sections.length > 0) {
  251. course.teacher = course.teacher || "未知教师";
  252. course.position = course.position || "未知地点";
  253. console.log(` ✓ 完整课程:`, {
  254. name: course.name,
  255. teacher: course.teacher,
  256. position: course.position,
  257. day: course.day,
  258. weeks: `${course.weeks.length}周`,
  259. sections: course.sections
  260. });
  261. result.push(course);
  262. } else {
  263. console.warn(` ✗ 信息不完整:`, {
  264. name: course.name || '无',
  265. teacher: course.teacher || '无',
  266. weeks: course.weeks.length,
  267. sections: course.sections.length
  268. });
  269. }
  270. });
  271. });
  272. });
  273. console.log(`\n解析完成,共得到 ${result.length} 条课程记录(去重前)`);
  274. // 合并完全相同的课程(去重)
  275. const courseKeys = new Set();
  276. result.forEach(course => {
  277. // 生成课程唯一标识: 名称+老师+地点+星期+节次+周次
  278. const key = `${course.name}|${course.teacher}|${course.position}|${course.day}|${course.sections.join(',')}|${course.weeks.join(',')}`;
  279. if (!courseKeys.has(key)) {
  280. courseKeys.add(key);
  281. uniqueCourses.push(course);
  282. } else {
  283. console.log(` 跳过重复课程: ${course.name} (${course.teacher})`);
  284. }
  285. });
  286. console.log(`去重后剩余 ${uniqueCourses.length} 条课程记录`);
  287. } catch (err) {
  288. console.error('解析课程表出错:', err);
  289. throw new Error('解析课程表失败: ' + err.message);
  290. }
  291. return uniqueCourses;
  292. }
  293. /**
  294. * 转换课程数据格式以符合时光课表规范
  295. * @param {Array} rawCourses 原始课程数据
  296. * @returns {Array} 转换后的课程数据
  297. */
  298. function convertCoursesToStandardFormat(rawCourses) {
  299. const validCourses = [];
  300. rawCourses.forEach((course, index) => {
  301. try {
  302. // 处理节次:将原始格式转换为startSection和endSection
  303. let startSection = 1;
  304. let endSection = 1;
  305. if (course.sections && course.sections.length > 0) {
  306. const sections = course.sections.sort((a, b) => a - b);
  307. startSection = sections[0];
  308. endSection = sections[sections.length - 1];
  309. }
  310. // 验证必需字段
  311. if (!startSection || !endSection || startSection < 1 || endSection < 1) {
  312. console.error(`课程 ${index + 1} 缺少有效的节次信息:`, course);
  313. throw new Error(`课程节次信息无效: startSection=${startSection}, endSection=${endSection}`);
  314. }
  315. if (!course.day || course.day < 1 || course.day > 7) {
  316. console.error(`课程 ${index + 1} 星期数据无效:`, course);
  317. throw new Error(`课程星期数据无效: day=${course.day}`);
  318. }
  319. if (!course.weeks || course.weeks.length === 0) {
  320. console.error(`课程 ${index + 1} 缺少周次信息:`, course);
  321. throw new Error(`课程周次信息缺失`);
  322. }
  323. const convertedCourse = {
  324. name: course.name || "未知课程",
  325. teacher: course.teacher || "未知教师",
  326. position: course.position || "未知地点",
  327. day: course.day,
  328. startSection: startSection,
  329. endSection: endSection,
  330. weeks: course.weeks
  331. };
  332. validCourses.push(convertedCourse);
  333. } catch (err) {
  334. console.error(`转换课程 ${index + 1} 时出错:`, err.message);
  335. // 如果任何课程转换失败,抛出错误
  336. throw new Error(`课程数据验证失败: ${err.message}`);
  337. }
  338. });
  339. return validCourses;
  340. }
  341. /**
  342. * 生成时间段配置
  343. * @param {number} campusIdx 校区索引(0为龙泉校区,1为安宁校区)
  344. * @returns {Array} 时间段数组
  345. */
  346. function generateTimeSlots(campusIdx = 0) {
  347. // 云南财经大学默认时间配置(龙泉校区)
  348. const timeSlots = [
  349. { "number": 1, "startTime": "08:00", "endTime": "08:40" },
  350. { "number": 2, "startTime": "08:50", "endTime": "09:30" },
  351. { "number": 3, "startTime": "10:00", "endTime": "10:40" },
  352. { "number": 4, "startTime": "10:50", "endTime": "11:30" },
  353. { "number": 5, "startTime": "11:40", "endTime": "12:20" },
  354. { "number": 6, "startTime": "14:30", "endTime": "15:10" },
  355. { "number": 7, "startTime": "15:20", "endTime": "16:00" },
  356. { "number": 8, "startTime": "16:30", "endTime": "17:10" },
  357. { "number": 9, "startTime": "17:20", "endTime": "18:00" },
  358. { "number": 10, "startTime": "18:10", "endTime": "18:30" },
  359. { "number": 11, "startTime": "19:00", "endTime": "19:40" },
  360. { "number": 12, "startTime": "19:50", "endTime": "20:30" },
  361. { "number": 13, "startTime": "20:50", "endTime": "21:30" },
  362. { "number": 14, "startTime": "21:40", "endTime": "22:20" }
  363. ];
  364. // 安宁校区时间配置
  365. const timeSlots_AN = [
  366. { "number": 1, "startTime": "08:20", "endTime": "09:00" },
  367. { "number": 2, "startTime": "09:10", "endTime": "09:50" },
  368. { "number": 3, "startTime": "10:10", "endTime": "10:50" },
  369. { "number": 4, "startTime": "11:00", "endTime": "11:40" },
  370. { "number": 5, "startTime": "11:50", "endTime": "12:30" },
  371. { "number": 6, "startTime": "14:00", "endTime": "14:40" },
  372. { "number": 7, "startTime": "14:50", "endTime": "15:30" },
  373. { "number": 8, "startTime": "15:40", "endTime": "16:20" },
  374. { "number": 9, "startTime": "16:40", "endTime": "17:20" },
  375. { "number": 10, "startTime": "17:30", "endTime": "18:10" },
  376. { "number": 11, "startTime": "19:00", "endTime": "19:40" },
  377. { "number": 12, "startTime": "19:50", "endTime": "20:30" },
  378. { "number": 13, "startTime": "20:40", "endTime": "21:20" }
  379. ];
  380. return campusIdx === 1 ? timeSlots_AN : timeSlots;
  381. }
  382. // ========== 网络请求功能 ==========
  383. /**
  384. * 获取学期列表
  385. * @returns {Promise<Object>} 学期列表数据
  386. */
  387. async function getSemesterList() {
  388. AndroidBridge.showToast('正在获取学期列表...');
  389. const response = await fetch('/jsxsd/xskb/xskb_list.do', { method: 'GET', credentials: 'include' });
  390. const htmlText = await response.text();
  391. const parser = new DOMParser();
  392. let doc = parser.parseFromString(htmlText, 'text/html');
  393. const selectElem = doc.getElementById('xnxq01id');
  394. let semesters = [];
  395. let semesterValues = [];
  396. let defaultIndex = 0;
  397. if (selectElem) {
  398. const options = selectElem.querySelectorAll('option');
  399. options.forEach((opt, index) => {
  400. semesters.push(opt.innerText.trim());
  401. semesterValues.push(opt.value);
  402. if (opt.hasAttribute('selected') || opt.selected) {
  403. defaultIndex = index;
  404. }
  405. });
  406. }
  407. return { semesters, semesterValues, defaultIndex, htmlText };
  408. }
  409. /**
  410. * 根据学期值获取课程表HTML
  411. * @param {string} semesterValue 学期参数值
  412. * @returns {Promise<string>} 课程表HTML
  413. */
  414. async function fetchScheduleForSemester(semesterValue) {
  415. let formData = new URLSearchParams();
  416. formData.append('xnxq01id', semesterValue);
  417. const postResponse = await fetch('/jsxsd/xskb/xskb_list.do', {
  418. method: 'POST',
  419. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  420. body: formData.toString(),
  421. credentials: 'include'
  422. });
  423. return await postResponse.text();
  424. }
  425. // ========== 主要功能函数 ==========
  426. /**
  427. * 获取和解析课程数据
  428. * @param {string} html 课程表HTML
  429. * @returns {Array|null} 课程数组或null
  430. */
  431. async function fetchAndParseCourses(html) {
  432. try {
  433. console.log('开始解析获取到的课程表HTML...');
  434. if (!html) {
  435. console.warn('未传入有效的课程表HTML');
  436. return null;
  437. }
  438. console.log('成功获取课程表HTML,开始解析...');
  439. // 解析课程数据
  440. const rawCourses = parseScheduleHtml(html);
  441. if (!rawCourses || rawCourses.length === 0) {
  442. console.warn('未解析到课程数据');
  443. return null;
  444. }
  445. console.log(`原始解析到 ${rawCourses.length} 条课程记录`);
  446. // 转换为标准格式
  447. const courses = convertCoursesToStandardFormat(rawCourses);
  448. console.log(`转换为标准格式后有 ${courses.length} 门课程`);
  449. return courses;
  450. } catch (error) {
  451. console.error('获取或解析课程数据失败:', error);
  452. return null;
  453. }
  454. }
  455. /**
  456. * 保存课程数据到时光课表
  457. * @param {Array} courses 课程数组
  458. * @returns {boolean} 保存是否成功
  459. */
  460. async function saveCourses(courses) {
  461. try {
  462. console.log(`正在保存 ${courses.length} 门课程...`);
  463. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  464. console.log('课程数据保存成功');
  465. return true;
  466. } catch (error) {
  467. console.error('保存课程失败:', error);
  468. return false;
  469. }
  470. }
  471. /**
  472. * 导入预设时间段到时光课表
  473. * @param {number} campusIdx 校区索引
  474. * @returns {boolean} 导入是否成功
  475. */
  476. async function importPresetTimeSlots(campusIdx = 0) {
  477. try {
  478. console.log('正在导入时间段配置...');
  479. const presetTimeSlots = generateTimeSlots(campusIdx);
  480. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  481. console.log('时间段配置导入成功');
  482. return true;
  483. } catch (error) {
  484. console.error('导入时间段失败:', error);
  485. return false;
  486. }
  487. }
  488. // ========== 主执行流程 ==========
  489. /**
  490. * 主导入函数:云南财经大学课程表导入
  491. */
  492. async function importYnufeCourseSchedule() {
  493. // 检查是否在登录页面
  494. if (isLoginPage()) {
  495. console.log('检测到在登录页面,终止导入');
  496. AndroidBridge.showToast('请先登录教务系统!');
  497. return; // 直接返回,不抛出错误,不调用notifyTaskCompletion
  498. }
  499. try {
  500. console.log('云南财经大学课程导入开始...');
  501. // 获取学期列表
  502. let semesters = [], semesterValues = [], defaultIndex = 0, htmlText = '';
  503. try {
  504. const listData = await getSemesterList();
  505. semesters = listData.semesters;
  506. semesterValues = listData.semesterValues;
  507. defaultIndex = listData.defaultIndex;
  508. htmlText = listData.htmlText;
  509. } catch (e) {
  510. console.warn('获取学期列表网络请求失败:', e);
  511. }
  512. let targetHtml = htmlText;
  513. let selectedCampusIdx = 0; // 默认龙泉校区
  514. if (semesters && semesters.length > 0) {
  515. // 循环直到用户选择学期才进行下一步(强制不可取消)
  516. let selectedIdx = null;
  517. while (true) {
  518. selectedIdx = await window.AndroidBridgePromise.showSingleSelection(
  519. "选择学期",
  520. JSON.stringify(semesters),
  521. -1
  522. );
  523. if (selectedIdx !== null && selectedIdx !== -1) {
  524. break;
  525. }
  526. AndroidBridge.showToast("必须选择一个学期才能继续导入!");
  527. }
  528. // 获取对应学期的课表HTML
  529. AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 的课表...`);
  530. targetHtml = await fetchScheduleForSemester(semesterValues[selectedIdx]);
  531. const campuses = ["龙泉校区(默认)", "安宁校区"];
  532. selectedCampusIdx = await window.AndroidBridgePromise.showSingleSelection(
  533. "选择校区",
  534. JSON.stringify(campuses),
  535. 0
  536. );
  537. // 如果用户未选择,或者点击了取消,默认设为龙泉校区
  538. if (selectedCampusIdx === null || selectedCampusIdx === -1) {
  539. selectedCampusIdx = 0;
  540. }
  541. }
  542. // 获取和解析课程数据
  543. let courses = await fetchAndParseCourses(targetHtml);
  544. // 如果没有获取到任何课程
  545. if (!courses || courses.length === 0) {
  546. console.log('未获取到课程数据');
  547. // 检查是否真的是空课表
  548. if (targetHtml && targetHtml.includes('kbtable')) {
  549. // 找到了课表元素但没有课程,是真的空课表
  550. console.log('检测到空课表');
  551. AndroidBridge.showToast('当前课表为空');
  552. courses = []; // 返回空数组
  553. } else {
  554. // 找不到课表元素,解析失败
  555. AndroidBridge.showToast('获取课表失败,请检查网络和页面状态');
  556. throw new Error('未找到课表数据');
  557. }
  558. } else {
  559. console.log(`成功解析 ${courses.length} 门课程`);
  560. }
  561. // 保存课程数据
  562. const saveResult = await saveCourses(courses);
  563. if (!saveResult) {
  564. AndroidBridge.showToast('保存课程失败');
  565. throw new Error('保存课程数据失败');
  566. }
  567. // 导入时间段配置
  568. const timeSlotResult = await importPresetTimeSlots(selectedCampusIdx);
  569. if (!timeSlotResult) {
  570. AndroidBridge.showToast('导入时间段配置失败');
  571. throw new Error('导入时间段失败');
  572. }
  573. // 成功
  574. if (courses.length > 0) {
  575. const campusName = selectedCampusIdx === 1 ? "安宁校区" : "龙泉校区";
  576. AndroidBridge.showToast(`成功导入 ${courses.length} 门课程!(${campusName})`);
  577. }
  578. console.log('课程导入完成');
  579. return true;
  580. } catch (error) {
  581. console.error('导入过程出错:', error);
  582. AndroidBridge.showToast('导入失败: ' + error.message);
  583. return false;
  584. }
  585. }
  586. /**
  587. * 启动导入流程并处理完成信号
  588. */
  589. async function runImportFlow() {
  590. const success = await importYnufeCourseSchedule();
  591. // 只有成功导入时才发送完成信号
  592. if (success) {
  593. AndroidBridge.notifyTaskCompletion();
  594. }
  595. return success;
  596. }
  597. // 启动导入流程
  598. runImportFlow();