// 武汉科技大学教务系统课程表导入脚本 (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(); text.split(",").forEach(function (part) { var range = part.trim().split("-").map(function (item) { return parseInt(item, 10); }); if (!Number.isFinite(range[0])) return; var end = Number.isFinite(range[1]) ? range[1] : range[0]; for (var week = range[0]; week <= end; week++) weeks.add(week); }); return Array.from(weeks).sort(function (a, b) { return a - b; }); } // 将节次文本解析为连续的节次数组。 function parsePeriods(value) { var match = normalizeText(value).match(/\[([^\]]+)\]/); if (!match) return []; var numbers = match[1].match(/\d+/g); if (!numbers || numbers.length === 0) return []; var start = parseInt(numbers[0], 10); var end = parseInt(numbers[numbers.length - 1], 10); var periods = []; for (var period = start; period <= end; period++) periods.push(period); return periods; } // 查找主页面或同源 iframe 中的课表文档。 function findScheduleDocument() { if (document.querySelector("#kbtable")) return document; return Array.from(document.querySelectorAll("iframe")).reduce(function (found, frame) { if (found) return found; try { var frameDoc = frame.contentDocument || frame.contentWindow.document; return frameDoc && frameDoc.querySelector("#kbtable") ? frameDoc : null; } catch (error) { return null; } }, null); } // 解析单个课程块,生成应用需要的课程对象。 function parseCourseBlock(block) { var holder = document.createElement("div"); holder.innerHTML = block; var lines = Array.from(holder.querySelectorAll("font[title]")).reduce(function (result, font) { result[font.getAttribute("title")] = normalizeText(font.textContent); return result; }, {}); var firstLine = holder.innerHTML.split(//i)[0]; var nameHolder = document.createElement("div"); nameHolder.innerHTML = firstLine; var name = normalizeText(nameHolder.textContent); var weekPeriod = lines["周次(节次)"] || ""; var periods = parsePeriods(weekPeriod); var weeks = parseWeeks(weekPeriod.split("[")[0]); if (!name || weeks.length === 0 || periods.length === 0) return null; return { name: name, teacher: lines["老师"] || "", position: lines["教室"] || "未指定", weeks: weeks, startSection: periods[0], endSection: periods[periods.length - 1] }; } // 遍历课表单元格,解析课程并合并重复的课程记录。 function parseCourses(doc) { var table = doc.querySelector("#kbtable"); if (!table) return []; var courses = new Map(); Array.from(table.querySelectorAll("div.kbcontent")).forEach(function (div) { var idParts = (div.id || "").split("-"); var weekday = parseInt(idParts[idParts.length - 2], 10); if (!Number.isFinite(weekday) || weekday < 1 || weekday > 7) return; var blocks = div.innerHTML.split(/\s*[-—]{10,}\s*/i); blocks.forEach(function (block) { var course = parseCourseBlock(block); if (!course) return; course.day = weekday; var key = [course.name, course.teacher, course.position, weekday, course.weeks.join(",")].join("|"); if (!courses.has(key)) { courses.set(key, course); return; } var existing = courses.get(key); existing.startSection = Math.min(existing.startSection, course.startSection); existing.endSection = Math.max(existing.endSection, course.endSection); }); }); return Array.from(courses.values()).sort(function (a, b) { return a.day - b.day || a.startSection - b.startSection || a.name.localeCompare(b.name); }); } // 使用 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, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, credentials: "include", body: body }); if (!response.ok) throw new Error("课表请求失败:HTTP " + response.status); 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); })); var config = { semesterTotalWeeks: Number.isFinite(maxWeek) && maxWeek > 0 ? maxWeek : 20, firstDayOfWeek: 1, defaultClassDuration: 45, defaultBreakDuration: 5 }; if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveCourseConfig) { await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config)); } 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); var courses = parseCourses(scheduleDoc); if (courses.length === 0) { toast("未解析到课程,请确认已登录且课表页面已加载完成"); return; } var saved = await saveData(courses, getCampusTimeSlots(campusIndex)); if (!saved) { toast("课程导入失败"); return; } toast("成功导入 " + courses.length + " 门课程"); if (window.shiguangBridge.notifyTaskCompletion) window.shiguangBridge.notifyTaskCompletion(); } catch (error) { console.error("WUST import failed:", error); toast("导入失败:" + error.message); } } // 检查桥接 API 可用后启动导入流程。 if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveImportedCourses) { runImport(); } })();