jnu_01.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. //拾光课程表适配JNU脚本(fetch API)
  2. //本脚本的时间段自动导入仅适配珠海校区,其它校区可自行重新设置上课时间段
  3. //生成年份数组
  4. const yearArr = [];
  5. for(let i=1990; i<=2100; i++){
  6. yearArr.push(i);
  7. }
  8. //获取本月最大日期
  9. function getDaysInMonth(year, month) {
  10. return new Date(year, month, 0).getDate();
  11. }
  12. //根据URL特征判断当前所在界面
  13. function checkCurrentPage() {
  14. const currenturl = window.location.href.toLowerCase(); //获取当前URL并转为小写
  15. if (currenturl.includes("login?")) {
  16. return 1; //未登录,返回值1
  17. }
  18. if (currenturl.includes("new/index.html")) {
  19. return 2; //在新教务系统首页,没有进入课表,返回值2
  20. }
  21. if (currenturl.includes("jwapp/sys/wdkb")) {
  22. return 0; //成功登录且进入我的课表界面,返回值0
  23. }
  24. return -1; //未知错误,返回值-1
  25. }
  26. //显示一个公告信息弹窗
  27. async function confirmAlert() {
  28. try {
  29. const confirmed = await window.AndroidBridgePromise.showAlert(
  30. "重要提醒",
  31. "自动加载上课时间段仅支持珠海校区,\n每周第一天默认为星期日,可自行修改。\n是否确认获取课表?",
  32. "确认",
  33. );
  34. if (confirmed) {
  35. AndroidBridge.showToast("正在尝试导入课表...");
  36. return true; // 成功时返回 true
  37. } else {
  38. AndroidBridge.showToast("已取消!");
  39. return false; // 用户取消时返回 false
  40. }
  41. } catch (error) {
  42. AndroidBridge.showToast("显示弹窗出错!" + error.message);
  43. return false; // 出现错误时也返回 false
  44. }
  45. }
  46. //显示一个是否手动输入学年学期信息的弹窗
  47. async function confirmGetXNXQAlert() {
  48. try {
  49. const confirmed = await window.AndroidBridgePromise.showAlert(
  50. "重要提醒",
  51. "是否确认手动输入学年学期信息?",
  52. "确认",
  53. );
  54. if (confirmed) {
  55. return true; // 成功时返回 true
  56. } else {
  57. AndroidBridge.showToast("已取消!");
  58. return false; // 用户取消时返回 false
  59. }
  60. } catch (error) {
  61. AndroidBridge.showToast("显示弹窗出错!" + error.message);
  62. return false; // 出现错误时也返回 false
  63. }
  64. }
  65. //向用户获取学年信息
  66. async function getSession() {
  67. try {
  68. const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
  69. "请选择学年的起始年(如2025-2026学年的2025)",
  70. JSON.stringify(yearArr),
  71. 36
  72. );
  73. if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < yearArr.length) {
  74. AndroidBridge.showToast("你选择了" + yearArr[selectedIndex] + '-' + (Number(yearArr[selectedIndex])+1) + "学年");
  75. return (yearArr[selectedIndex] + '-' + (Number(yearArr[selectedIndex])+1));
  76. } else {
  77. AndroidBridge.showToast("用户取消了选择!");
  78. return -1; // 用户取消时返回-1
  79. }
  80. } catch (error) {
  81. AndroidBridge.showToast("显示列表出错!" + error.message);
  82. return -1; // 出现错误时也返回-1
  83. }
  84. }
  85. //向用户获取学期信息
  86. async function getSemester() {
  87. try {
  88. const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
  89. "请选择学期(1-第一学期,2-第二学期)",
  90. JSON.stringify([1,2]),
  91. 1
  92. );
  93. if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < 2) {
  94. if(selectedIndex === 0) {
  95. AndroidBridge.showToast("你选择了第一学期");
  96. return 1;
  97. }else{
  98. AndroidBridge.showToast("你选择了第二学期");
  99. return 2;
  100. }
  101. } else {
  102. AndroidBridge.showToast("用户取消了选择!");
  103. return -1; // 用户取消时返回 -1
  104. }
  105. } catch (error) {
  106. AndroidBridge.showToast("显示列表出错!" + error.message);
  107. return -1; // 出现错误时也返回 -1
  108. }
  109. }
  110. //导入预设时间段
  111. async function importPresetTimeSlots() {
  112. //预设节次信息,仅适用于珠海校区
  113. const timeSlots = [
  114. {"number":1,"startTime":"08:00","endTime":"08:45"},
  115. {"number":2,"startTime":"08:55","endTime":"09:40"},
  116. {"number":3,"startTime":"10:00","endTime":"10:45"},
  117. {"number":4,"startTime":"10:55","endTime":"11:40"},
  118. {"number":5,"startTime":"12:40","endTime":"13:25"},
  119. {"number":6,"startTime":"13:35","endTime":"14:20"},
  120. {"number":7,"startTime":"14:30","endTime":"15:15"},
  121. {"number":8,"startTime":"15:25","endTime":"16:10"},
  122. {"number":9,"startTime":"16:20","endTime":"17:05"},
  123. {"number":10,"startTime":"17:15","endTime":"18:00"},
  124. {"number":11,"startTime":"19:00","endTime":"19:45"},
  125. {"number":12,"startTime":"19:55","endTime":"20:40"},
  126. {"number":13,"startTime":"20:50","endTime":"21:35"}
  127. ]
  128. try {
  129. const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  130. if (result === true) {
  131. //AndroidBridge.showToast("时间段导入成功!");
  132. } else {
  133. AndroidBridge.showToast("时间段导入失败!");
  134. }
  135. } catch (error) {
  136. AndroidBridge.showToast("导入时间段失败: " + error.message);
  137. }
  138. }
  139. //使用DOM方法自动获取当前课表的学年学期信息
  140. function getSessionSemester() {
  141. const oriData = document.getElementById('dqxnxq2');
  142. if (!oriData) {
  143. return -1;
  144. }
  145. const xnxqID = oriData.getAttribute('value');
  146. if (!xnxqID) {
  147. return -1;
  148. }
  149. return xnxqID;
  150. }
  151. //使用fetch API 获取课程表信息
  152. async function getCourses(xnxqID) {
  153. const fetchConfig = {
  154. url:'https://jw.jnu.edu.cn/jwapp/sys/wdkb/modules/xskcb/xskcb.do',
  155. method:'POST',
  156. params:{
  157. XNXQDM:xnxqID
  158. }
  159. }
  160. try {
  161. const response = await fetch(fetchConfig.url,
  162. {method:fetchConfig.method,
  163. headers:{'content-type':'application/x-www-form-urlencoded; charset=UTF-8'},
  164. credentials:'include',
  165. body:new URLSearchParams(fetchConfig.params)});
  166. if (!response.ok) {
  167. throw new Error(`请求失败:${response.status} ${response.statusText}`);
  168. }
  169. const courseData = await response.json();
  170. if (courseData.datas.xskcb.totalSize === 0){
  171. AndroidBridge.showToast("获取到的课表为空课表,请检查学年学期信息!")
  172. return -1;
  173. }
  174. AndroidBridge.showToast("成功获取课程表信息,共" + courseData.datas.xskcb.totalSize + "门课程");
  175. return courseData;
  176. }catch (error){
  177. AndroidBridge.showToast("获取课程表失败:" + error.message);
  178. return -1;
  179. }
  180. }
  181. //处理原始数据中的周数,输出对应数组
  182. function handleWeekNum(oriData){
  183. const result = [];
  184. if(oriData.includes('(')){
  185. const weekStr = oriData.slice(0,-4);//去掉最后四位
  186. const startWeek = weekStr.split('-')[0];
  187. const endWeek = weekStr.split('-')[1];
  188. if(oriData.includes('单')){
  189. for(let i = Number(startWeek); i<=Number(endWeek); i++){
  190. if(i%2 !== 0){
  191. result.push(i);
  192. }
  193. }
  194. }else{
  195. for(let i = Number(startWeek); i<=Number(endWeek); i++){
  196. if(i%2 === 0){
  197. result.push(i);
  198. }
  199. }
  200. }
  201. }else{
  202. const weekStr = oriData.slice(0,-1); //去掉最后一位
  203. const startWeek = weekStr.split('-')[0];
  204. const endWeek = weekStr.split('-')[1];
  205. for(let i = Number(startWeek); i<=Number(endWeek); i++){
  206. result.push(i);
  207. }
  208. }
  209. return result;
  210. }
  211. //处理原始数据中的节次信息
  212. function handleSectionNum(oriData) {
  213. const result = [];
  214. const coursesNumStr = oriData.slice(1,-1); //去掉最后一位和第一位
  215. const startSection = coursesNumStr.split('-')[0];
  216. const endSection = coursesNumStr.split('-')[1];
  217. result.push(Number(startSection)); //第一位为开始节次
  218. result.push(Number(endSection)); //第二位为结束节次
  219. return result;
  220. }
  221. //处理原始数据中的星期信息
  222. function handleDayNum(OriData) {
  223. const weekMap = [
  224. "星期一",
  225. "星期二",
  226. "星期三",
  227. "星期四",
  228. "星期五",
  229. "星期六",
  230. "星期日",
  231. ]
  232. for(let i=0; i<7; i++){
  233. if(OriData === weekMap[i])
  234. return i+1;
  235. }
  236. return null;
  237. }
  238. // 解析得到的JSON数据
  239. function analysisCoursesInfo(oriCourseData) {
  240. const courses = [];
  241. const totalCourseNum = oriCourseData.datas.xskcb.totalSize;
  242. if(totalCourseNum === 0){
  243. return courses;
  244. }
  245. const coursesRows = oriCourseData.datas.xskcb.rows || [];
  246. for(let i=0; i<totalCourseNum; i++){
  247. const courseName = coursesRows[i].KCM ?? "未知课程";
  248. const teacher = coursesRows[i].SKJS ?? "未知教师";
  249. const position = coursesRows[i].JASMC ?? "未知地点";
  250. const courseTime = coursesRows[i].SKSJ.split(' '); //第一为周次,第二位为天次,第三位为节次
  251. const day = handleDayNum(courseTime[1]); //得到星期几上课
  252. const sectionInfo = {
  253. startSection:handleSectionNum(courseTime[2])[0], //开始节次
  254. endSection:handleSectionNum(courseTime[2])[1] //结束节次
  255. }
  256. const weeks = handleWeekNum(courseTime[0]);
  257. const courseInfo = {
  258. "name": courseName, // 课程名称 (String)
  259. "teacher": teacher, // 教师姓名 (String)
  260. "position": position, // 上课地点 (String)
  261. "day": day, // 星期几 (Int, 1=周一, 7=周日)
  262. "startSection": sectionInfo.startSection, // 开始节次 (Int, 如果 isCustomTime 为 false 或未提供,则必填)
  263. "endSection": sectionInfo.endSection, // 结束节次 (Int, 如果 isCustomTime 为 false 或未提供,则必填)
  264. "weeks": weeks, // 上课周数 (Int Array, 必须是数字数组,例如 [1, 3, 5, 7])
  265. }
  266. courses.push(courseInfo);
  267. }
  268. return courses;
  269. }
  270. //自动获取总星期数
  271. function autoGetTotalWeekNum(courses) {
  272. let totalWeekNum = 0;
  273. let currentNum = 0;
  274. for(let i=0; i<courses.length; i++){
  275. currentNum = courses[i].weeks[courses[i].weeks.length-1];
  276. if(currentNum > totalWeekNum) {
  277. totalWeekNum = currentNum;
  278. }
  279. }
  280. return totalWeekNum;
  281. }
  282. //导入课程数据,同时返回得到的总星期数
  283. async function saveCourses(courseData) {
  284. let xnxqID = getSessionSemester();
  285. if (xnxqID === -1) {
  286. AndroidBridge.showToast("无法自动获取学年学期信息!");
  287. if(await confirmGetXNXQAlert()){
  288. const session = await getSession();
  289. if(session === -1){
  290. return -1;
  291. }
  292. const semester = await getSemester();
  293. if(semester === -1){
  294. return -1;
  295. }
  296. xnxqID = (session + '-' + semester);
  297. }else {
  298. return -1;
  299. }
  300. }else{
  301. AndroidBridge.showToast("自动获取到学年学期信息:" + xnxqID);
  302. }
  303. const rawResponse = await getCourses(xnxqID);
  304. if(rawResponse === -1) {
  305. return -1;
  306. }
  307. const courses = analysisCoursesInfo(rawResponse);
  308. const totalWeeksNum = autoGetTotalWeekNum(courses);
  309. try {
  310. const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  311. if (result === true) {
  312. //AndroidBridge.showToast("课程导入成功!");
  313. return totalWeeksNum;
  314. } else {
  315. AndroidBridge.showToast("课程导入失败!");
  316. return -1
  317. }
  318. } catch (error) {
  319. AndroidBridge.showToast("导入课程失败: " + error.message);
  320. return -1;
  321. }
  322. }
  323. //导入课表配置
  324. async function saveConfig(semesterTotalWeeks) {
  325. // 注意:只传入要修改的字段,其他字段(如 semesterTotalWeeks)会使用 Kotlin 模型中的默认值
  326. let semesterStartData = "";
  327. let choseResult = 1;
  328. //显示三次单项选择框,向用户获取开学时间
  329. try {
  330. const yearIndex = await window.AndroidBridgePromise.showSingleSelection(
  331. "请选择开学时间(年份)",
  332. JSON.stringify(yearArr),
  333. 36
  334. );
  335. if (yearIndex !== null && yearIndex >= 0 && yearIndex < yearArr.length) {
  336. const year = yearArr[yearIndex];
  337. const monthIndex = await window.AndroidBridgePromise.showSingleSelection(
  338. "请选择开学时间(月份)",
  339. JSON.stringify([1,2,3,4,5,6,7,8,9,10,11,12]),
  340. 2
  341. );
  342. if (monthIndex !== null && monthIndex >= 0 && monthIndex < 12) {
  343. const month =[1,2,3,4,5,6,7,8,9,10,11,12][monthIndex];
  344. const dayArr = [];
  345. for (let i = 1; i <= getDaysInMonth(year,month); i++) {
  346. dayArr.push(i);
  347. }
  348. const dayIndex = await window.AndroidBridgePromise.showSingleSelection(
  349. "请选择开学时间(日期)",
  350. JSON.stringify(dayArr),
  351. 7
  352. );
  353. if (dayIndex !== null && dayIndex >= 0 && dayIndex < dayArr.length) {
  354. const day = dayArr[dayIndex];
  355. const yearStr = year.toString();
  356. const monthStr = month.toString().padStart(2,"0"); //补充为两位
  357. const dayStr = day.toString().padStart(2,"0"); //补充为两位
  358. semesterStartData = yearStr + '-' + monthStr + '-' + dayStr;
  359. AndroidBridge.showToast("你选择的开学时间为:" + semesterStartData);
  360. } else {
  361. choseResult = 0;
  362. }
  363. } else {
  364. choseResult = 0;
  365. }
  366. } else {
  367. choseResult = 0;
  368. }
  369. } catch (error) {
  370. AndroidBridge.showToast("显示列表出错!" + error.message);
  371. return -1; // 出现错误时也返回 -1
  372. }
  373. if(!choseResult){
  374. AndroidBridge.showToast("用户取消了选择,将按默认开学日期2026-3-9导入配置");
  375. semesterStartData = "2026-03-09";
  376. }
  377. try {
  378. const courseConfigData = {
  379. "semesterStartDate": semesterStartData, //月份要使用两位数,否则软件会崩溃
  380. "semesterTotalWeeks": Number(semesterTotalWeeks),
  381. "defaultClassDuration": 45,
  382. "defaultBreakDuration": 10,
  383. "firstDayOfWeek": 7
  384. };
  385. const configJsonString = JSON.stringify(courseConfigData);
  386. const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
  387. if (result === true) {
  388. return 0;
  389. //AndroidBridge.showToast("课表配置导入成功!");
  390. } else {
  391. AndroidBridge.showToast("课表配置导入失败");
  392. return -1;
  393. }
  394. } catch (error) {
  395. AndroidBridge.showToast("导入配置失败: " + error.message);
  396. return -1;
  397. }
  398. }
  399. /**
  400. * 编排这些异步操作,并在用户取消时停止后续执行。
  401. */
  402. async function runAllDemosSequentially() {
  403. //检查是否进入课表界面
  404. const currentPageNum = checkCurrentPage();
  405. if(currentPageNum === 1){
  406. AndroidBridge.showToast("请登录教务系统");
  407. return;
  408. }else if(currentPageNum === 2) {
  409. AndroidBridge.showToast("请进入我的课表界面");
  410. return;
  411. }else if(currentPageNum === -1) {
  412. AndroidBridge.showToast("未知错误");
  413. return;
  414. }
  415. //确认窗口
  416. const alertResult = await confirmAlert();
  417. if (!alertResult) {
  418. return; // 用户取消,立即退出函数
  419. }
  420. const totalNum = await saveCourses();
  421. if(totalNum === -1) {
  422. return;
  423. }
  424. await importPresetTimeSlots();
  425. const saveConfigResult = await saveConfig(totalNum);
  426. if(saveConfigResult === -1)
  427. {
  428. return;
  429. }
  430. // 发送最终的生命周期完成信号
  431. AndroidBridge.notifyTaskCompletion();
  432. }
  433. runAllDemosSequentially();