jnu_01.js 14 KB

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