| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- const STANDARD_TIME_SLOTS = [
- { number: 1, startTime: "08:00", endTime: "08:45" },
- { number: 2, startTime: "08:55", endTime: "09:40" },
- { number: 3, startTime: "10:00", endTime: "10:45" },
- { number: 4, startTime: "10:55", endTime: "11:40" },
- { number: 5, startTime: "14:00", endTime: "14:45" },
- { number: 6, startTime: "14:50", endTime: "15:35" },
- { number: 7, startTime: "15:55", endTime: "16:40" },
- { number: 8, startTime: "16:45", endTime: "17:30" },
- { number: 9, startTime: "18:30", endTime: "19:15" },
- { number: 10, startTime: "19:20", endTime: "20:05" },
- { number: 11, startTime: "20:10", endTime: "20:55" }
- ];
- const CAMPUS_TIME_SLOTS = {
- "新庄校区": STANDARD_TIME_SLOTS,
- "淮安校区": STANDARD_TIME_SLOTS,
- "白马校区": [
- { number: 1, startTime: "08:30", endTime: "09:15" },
- { number: 2, startTime: "09:20", endTime: "10:05" },
- { number: 3, startTime: "10:25", endTime: "11:10" },
- { number: 4, startTime: "11:15", endTime: "12:00" },
- { number: 5, startTime: "14:00", endTime: "14:45" },
- { number: 6, startTime: "14:50", endTime: "15:35" },
- { number: 7, startTime: "15:55", endTime: "16:40" },
- { number: 8, startTime: "16:45", endTime: "17:30" },
- { number: 9, startTime: "18:30", endTime: "19:15" },
- { number: 10, startTime: "19:20", endTime: "20:05" },
- { number: 11, startTime: "20:10", endTime: "20:55" }
- ]
- };
- const CAMPUS_KEYWORDS = [
- { campus: "淮安校区", keywords: ["淮安校区"] },
- { campus: "白马校区", keywords: ["白马校区"] },
- { campus: "新庄校区", keywords: ["新庄校区"] }
- ];
- function cleanPosition(position) {
- return String(position || "")
- .replace(/^(新庄校区|淮安校区|白马校区)/, "")
- .replace(/[((]\d+人[))]\s*$/g, "")
- .trim() || "待定";
- }
- function showToast(message) {
- if (typeof window.AndroidBridge !== "undefined") {
- AndroidBridge.showToast(message);
- } else {
- console.log(message);
- }
- }
- function parseWeeks(rawText) {
- if (!rawText) return [];
- const weekPart = String(rawText)
- .replace(/\s+/g, "")
- .replace(/\(周\).*/, "")
- .replace(/周次[::]?/g, "");
- const weeks = new Set();
- weekPart.split(/[,,]/).forEach((segment) => {
- if (!segment) return;
- const isOdd = segment.includes("单");
- const isEven = segment.includes("双");
- const cleaned = segment.replace(/[单双周]/g, "");
- const match = cleaned.match(/^(\d+)(?:-(\d+))?$/);
- if (!match) return;
- const start = Number(match[1]);
- const end = Number(match[2] || match[1]);
- for (let week = start; week <= end; week++) {
- if (isOdd && week % 2 === 0) continue;
- if (isEven && week % 2 !== 0) continue;
- weeks.add(week);
- }
- });
- return Array.from(weeks).sort((a, b) => a - b);
- }
- function detectCampusOrNull(...texts) {
- const text = texts
- .filter(Boolean)
- .map((item) => String(item))
- .join(" ");
- for (const item of CAMPUS_KEYWORDS) {
- if (item.keywords.some((keyword) => text.includes(keyword))) {
- return item.campus;
- }
- }
- return null;
- }
- function readLineTexts(div) {
- const cloned = div.cloneNode(true);
- cloned.querySelectorAll(".item-box").forEach((node) => node.remove());
- return cloned.innerHTML
- .split(/<br\s*\/?>/i)
- .map((line) => line.replace(/<[^>]+>/g, "").trim())
- .filter(Boolean);
- }
- function extractCourseName(lines) {
- const metadataPrefixes = ["通知单编号", "班级", "备注"];
- const metadataKeywords = ["周", "节", "教师", "教室", "校区"];
- const nameLines = [];
- for (const line of lines) {
- if (!line) continue;
- if (metadataPrefixes.some((prefix) => line.startsWith(prefix))) break;
- if (metadataKeywords.some((keyword) => line.includes(keyword))) break;
- nameLines.push(line);
- }
- return nameLines.join("").trim();
- }
- function parseCourseBlock(blockHtml, fallbackDay) {
- const tempDiv = document.createElement("div");
- tempDiv.innerHTML = blockHtml;
- const lines = readLineTexts(tempDiv);
- if (!lines.length) return null;
- let name = extractCourseName(lines);
- const teacher = tempDiv.querySelector('font[title="教师"]')?.innerText.trim() || "未知";
- if (teacher && name && name !== teacher && name.endsWith(teacher)) {
- name = name.slice(0, -teacher.length).trim();
- }
- const positionRaw = tempDiv.querySelector('font[title="教室"]')?.innerText.trim() || "待定";
- const building = tempDiv.querySelector('font[title="教学楼"]')?.innerText.trim()
- || tempDiv.querySelector('font[name="jxlmc"]')?.innerText.trim()
- || "";
- const position = cleanPosition(positionRaw);
- const timeText = tempDiv.querySelector('font[title="周次(节次)"]')?.innerText.trim() || "";
- if (!timeText) return null;
- const weekMatch = timeText.match(/^(.*?)\(周\)/);
- const sectionMatch = timeText.match(/\[(\d+)(?:-(\d+))?(?:-(\d+))?(?:-(\d+))?节\]/);
- const weeks = parseWeeks(weekMatch ? weekMatch[1] : timeText);
- let startSection = 0;
- let endSection = 0;
- if (sectionMatch) {
- const values = sectionMatch.slice(1).filter(Boolean).map(Number);
- startSection = values[0];
- endSection = values[values.length - 1];
- }
- if (!name || !weeks.length || !startSection || !endSection) return null;
- return {
- name,
- teacher,
- position,
- day: fallbackDay,
- startSection,
- endSection,
- weeks,
- campus: detectCampusOrNull(positionRaw, building, lines.join(" "))
- };
- }
- function extractCoursesFromDoc(doc) {
- const table = doc.getElementById("timetable");
- if (!table) {
- throw new Error("未获取到课表表格,请确认当前账号已登录教务系统。");
- }
- const rows = Array.from(table.querySelectorAll("tr")).slice(1, -1);
- const courses = [];
- rows.forEach((row) => {
- const cells = Array.from(row.querySelectorAll("td"));
- cells.forEach((cell, index) => {
- const day = index + 1;
- const detailDivs = cell.querySelectorAll("div.kbcontent");
- detailDivs.forEach((div) => {
- const html = div.innerHTML.trim();
- if (!html || html === " ") return;
- const blocks = html.split(/-{10,}\s*<br\s*\/?>/i).filter((item) => item.trim());
- if (!blocks.length) blocks.push(html);
- blocks.forEach((block) => {
- const course = parseCourseBlock(block, day);
- if (course) {
- courses.push(course);
- }
- });
- });
- });
- });
- const uniqueMap = new Map();
- courses.forEach((course) => {
- const key = [
- course.name,
- course.teacher,
- course.position,
- course.day,
- course.startSection,
- course.endSection,
- course.weeks.join(",")
- ].join("|");
- if (!uniqueMap.has(key)) {
- uniqueMap.set(key, course);
- }
- });
- return Array.from(uniqueMap.values());
- }
- function choosePrimaryCampus(courses) {
- for (const course of courses) {
- if (course.campus) {
- return course.campus;
- }
- }
- return "新庄校区";
- }
- function normalizeCourses(courses, primaryCampus) {
- return courses.map((course) => {
- return {
- name: course.name,
- teacher: course.teacher,
- position: course.position,
- day: course.day,
- startSection: course.startSection,
- endSection: course.endSection,
- weeks: course.weeks,
- campus: course.campus || primaryCampus
- };
- });
- }
- function parseSemesterOptions(doc) {
- const select = doc.getElementById("xnxq01id");
- if (!select) return { labels: [], values: [], defaultIndex: 0 };
- const labels = [];
- const values = [];
- let defaultIndex = 0;
- Array.from(select.querySelectorAll("option")).forEach((option) => {
- labels.push(option.innerText.trim());
- values.push(option.value);
- if (option.selected || option.hasAttribute("selected")) {
- defaultIndex = labels.length - 1;
- }
- });
- return { labels, values, defaultIndex };
- }
- async function fetchTermDoc(termValue) {
- const body = new URLSearchParams();
- if (termValue) body.append("xnxq01id", termValue);
- const response = await fetch("/jsxsd/xskb/xskb_list.do", {
- method: termValue ? "POST" : "GET",
- headers: termValue ? { "Content-Type": "application/x-www-form-urlencoded" } : undefined,
- body: termValue ? body.toString() : undefined,
- credentials: "include"
- });
- const html = await response.text();
- return new DOMParser().parseFromString(html, "text/html");
- }
- async function pickTerm(doc) {
- const { labels, values, defaultIndex } = parseSemesterOptions(doc);
- if (!labels.length || typeof window.AndroidBridgePromise === "undefined") {
- return { doc, termLabel: labels[defaultIndex] || "" };
- }
- const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
- "请选择要导入的学期",
- JSON.stringify(labels),
- defaultIndex
- );
- if (selectedIndex === null || selectedIndex === -1) {
- throw new Error("已取消导入");
- }
- if (selectedIndex === defaultIndex) {
- return { doc, termLabel: labels[selectedIndex] };
- }
- const selectedDoc = await fetchTermDoc(values[selectedIndex]);
- return { doc: selectedDoc, termLabel: labels[selectedIndex] };
- }
- async function saveToApp(courses, primaryCampus) {
- const timeSlots = CAMPUS_TIME_SLOTS[primaryCampus];
- const allWeeks = courses.flatMap((course) => course.weeks || []);
- const semesterTotalWeeks = allWeeks.length ? Math.max(...allWeeks) : 20;
- if (typeof window.AndroidBridgePromise === "undefined") {
- console.log("Primary campus:", primaryCampus);
- console.log("Time slots:", timeSlots);
- console.log("Courses:", courses);
- alert(`解析完成:${primaryCampus},共 ${courses.length} 门课程。请查看控制台输出。`);
- return;
- }
- await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
- semesterTotalWeeks,
- firstDayOfWeek: 1
- }));
- await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- const appCourses = courses.map(({ campus, ...course }) => course);
- await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(appCourses));
- }
- async function runImportFlow() {
- try {
- showToast("正在获取 NJFU 课表数据...");
- const initialDoc = await fetchTermDoc("");
- const { doc, termLabel } = await pickTerm(initialDoc);
- const parsedCourses = extractCoursesFromDoc(doc);
- if (!parsedCourses.length) {
- throw new Error("未解析到课程,请确认当前账号已登录教务系统。");
- }
- const primaryCampus = choosePrimaryCampus(parsedCourses);
- const courses = normalizeCourses(parsedCourses, primaryCampus);
- await saveToApp(courses, primaryCampus);
- const message = `导入完成:${primaryCampus}${termLabel ? ` ${termLabel}` : ""}`;
- showToast(message);
- if (typeof window.AndroidBridge !== "undefined") {
- AndroidBridge.notifyTaskCompletion();
- }
- } catch (error) {
- console.error(error);
- showToast(`导入失败: ${error.message}`);
- }
- }
- runImportFlow();
|