浏览代码

feat: 添加武科大课表时间支持并支持按校区切换(青山/黄家湖) (#636)

Fuxin Nie 2 周之前
父节点
当前提交
4e995255fd
共有 1 个文件被更改,包括 78 次插入16 次删除
  1. 78 16
      resources/WUST/wust.js

+ 78 - 16
resources/WUST/wust.js

@@ -3,17 +3,69 @@
 (function () {
 	"use strict";
 
+	// 教务系统课表接口地址。
 	var SCHEDULE_PATH = "/jsxsd/xskb/xskb_list.do";
+	// 黄家湖校区的默认节次时间。
+	var DEFAULT_TIME_SLOTS = [
+		{ number: 1, startTime: "08:20", endTime: "09:05" },
+		{ number: 2, startTime: "09:15", endTime: "10:00" },
+		{ number: 3, startTime: "10:20", endTime: "11:05" },
+		{ number: 4, startTime: "11:15", endTime: "12:00" },
+		{ number: 5, startTime: "14:00", endTime: "14:45" },
+		{ number: 6, startTime: "14:55", endTime: "15:40" },
+		{ number: 7, startTime: "16:00", endTime: "16:45" },
+		{ number: 8, startTime: "16:55", endTime: "17:40" },
+		{ number: 9, startTime: "18:40", endTime: "19:25" },
+		{ number: 10, startTime: "19:35", endTime: "20:20" },
+		{ number: 11, startTime: "20:40", endTime: "21:25" },
+		{ number: 12, startTime: "21:35", endTime: "22:20" }
+	];
+	// 校区选项,选择结果用于确定课程时间。
+	var CAMPUS_OPTIONS = ["黄家湖校区", "青山校区"];
+
+	// 根据校区生成对应的节次时间,青山校区上午前四节提前。
+	function getCampusTimeSlots(campusIndex) {
+		var timeSlots = DEFAULT_TIME_SLOTS.map(function (slot) {
+			return {
+				number: slot.number,
+				startTime: slot.startTime,
+				endTime: slot.endTime
+			};
+		});
+		if (campusIndex !== 1) return timeSlots;
+
+		return timeSlots.map(function (slot) {
+			var minutes = slot.number <= 2 ? 20 : (slot.number <= 4 ? 10 : 0);
+			if (minutes === 0) return slot;
+			return {
+				number: slot.number,
+				startTime: shiftTime(slot.startTime, -minutes),
+				endTime: shiftTime(slot.endTime, -minutes)
+			};
+		});
+	}
+
+	// 按指定分钟数调整 HH:mm 格式的时间。
+	function shiftTime(time, offsetMinutes) {
+		var parts = time.split(":").map(Number);
+		var totalMinutes = parts[0] * 60 + parts[1] + offsetMinutes;
+		var hours = Math.floor(totalMinutes / 60).toString().padStart(2, "0");
+		var minutes = (totalMinutes % 60).toString().padStart(2, "0");
+		return hours + ":" + minutes;
+	}
+	// 通过应用桥接 API 显示提示信息。
 	function toast(message) {
 		if (window.shiguangBridge && window.shiguangBridge.showToast) {
 			window.shiguangBridge.showToast(message);
 		}
 	}
 
+	// 清理教务页面文本中的空格和非断行空格。
 	function normalizeText(value) {
 		return (value || "").replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
 	}
 
+	// 将周次文本解析为排序后的周次数组。
 	function parseWeeks(value) {
 		var text = normalizeText(value).replace(/\(周\)|周/g, "");
 		var weeks = new Set();
@@ -28,6 +80,7 @@
 		return Array.from(weeks).sort(function (a, b) { return a - b; });
 	}
 
+	// 将节次文本解析为连续的节次数组。
 	function parsePeriods(value) {
 		var match = normalizeText(value).match(/\[([^\]]+)\]/);
 		if (!match) return [];
@@ -40,6 +93,7 @@
 		return periods;
 	}
 
+	// 查找主页面或同源 iframe 中的课表文档。
 	function findScheduleDocument() {
 		if (document.querySelector("#kbtable")) return document;
 		return Array.from(document.querySelectorAll("iframe")).reduce(function (found, frame) {
@@ -53,6 +107,7 @@
 		}, null);
 	}
 
+	// 解析单个课程块,生成应用需要的课程对象。
 	function parseCourseBlock(block) {
 		var holder = document.createElement("div");
 		holder.innerHTML = block;
@@ -78,6 +133,7 @@
 		};
 	}
 
+	// 遍历课表单元格,解析课程并合并重复的课程记录。
 	function parseCourses(doc) {
 		var table = doc.querySelector("#kbtable");
 		if (!table) return [];
@@ -109,26 +165,27 @@
 		});
 	}
 
-	function parseTimeSlots(doc) {
-		var slots = new Map();
-		Array.from(doc.querySelectorAll("#kbtable tr")).forEach(function (row) {
-			var header = row.querySelector("th");
-			if (!header) return;
-			var match = normalizeText(header.textContent).match(/^(\d+).*?(\d{1,2}:\d{2})\s*[-~至]\s*(\d{1,2}:\d{2})/);
-			if (!match) return;
-			var number = parseInt(match[1], 10);
-			if (!slots.has(number)) {
-				slots.set(number, { number: number, startTime: match[2].padStart(5, "0"), endTime: match[3].padStart(5, "0") });
-			}
-		});
-		return Array.from(slots.values()).sort(function (a, b) { return a.number - b.number; });
+	// 使用 v2 桥接 API 让用户选择上课校区。
+	async function selectCampus() {
+		var selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
+			"选择上课校区",
+			JSON.stringify(CAMPUS_OPTIONS),
+			0
+		);
+		if (selectedIndex === null || selectedIndex < 0 || selectedIndex >= CAMPUS_OPTIONS.length) {
+			toast("已取消导入");
+			return null;
+		}
+		return selectedIndex;
 	}
 
+	// 获取当前页面选择的学期编号。
 	function getSemesterId(doc) {
 		var select = doc.querySelector("#xnxq01id");
 		return select ? select.value : "";
 	}
 
+	// 请求指定学期的课表页面并解析为文档对象。
 	async function fetchScheduleDocument(semesterId) {
 		var body = "cj0701id=&zc=&demo=&xnxq01id=" + encodeURIComponent(semesterId) + "&sfFD=1&wkbkc=1";
 		var response = await fetch(SCHEDULE_PATH, {
@@ -141,6 +198,7 @@
 		return new DOMParser().parseFromString(await response.text(), "text/html");
 	}
 
+	// 保存课程配置、校区时间段和课程数据。
 	async function saveData(courses, timeSlots) {
 		var maxWeek = Math.max.apply(null, courses.map(function (course) {
 			return Math.max.apply(null, course.weeks);
@@ -151,17 +209,20 @@
 			defaultClassDuration: 45,
 			defaultBreakDuration: 5
 		};
-		if (window.shiguangBridgePromise.saveCourseConfig) {
+		if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveCourseConfig) {
 			await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
 		}
-		if (timeSlots.length > 0 && window.shiguangBridgePromise.savePresetTimeSlots) {
+		if (timeSlots.length > 0 && window.shiguangBridgePromise && window.shiguangBridgePromise.savePresetTimeSlots) {
 			await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
 		}
 		return await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
 	}
 
+	// 编排校区选择、课表获取、解析和保存的完整导入流程。
 	async function runImport() {
 		try {
+			var campusIndex = await selectCampus();
+			if (campusIndex === null) return;
 			var currentDoc = findScheduleDocument() || document;
 			var semesterId = getSemesterId(currentDoc);
 			var scheduleDoc = currentDoc.querySelector("#kbtable") ? currentDoc : await fetchScheduleDocument(semesterId);
@@ -170,7 +231,7 @@
 				toast("未解析到课程,请确认已登录且课表页面已加载完成");
 				return;
 			}
-			var saved = await saveData(courses, parseTimeSlots(scheduleDoc));
+			var saved = await saveData(courses, getCampusTimeSlots(campusIndex));
 			if (!saved) {
 				toast("课程导入失败");
 				return;
@@ -183,6 +244,7 @@
 		}
 	}
 
+	// 检查桥接 API 可用后启动导入流程。
 	if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveImportedCourses) {
 		runImport();
 	}