| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // 上海立达学院 (lidapoly.edu.cn) 拾光课程表适配脚本
- // 强智教务 jsxsd,课表与校历均通过接口请求 HTML 解析,不依赖当前页面:
- // 课表 GET /shldzyjsxy_jsxsd/xskb/xskb_list.do?xnxq01id=<学期>
- // 校历 POST /shldzyjsxy_jsxsd/jxzl/jxzl_query (xnxq01id)
- // #kbtable 每大节一行、每天一格;详情 div (class kbcontent) 含 课名[学分][性质] / 老师 /
- // 班级(其内文本含 `周次(全部)[节次]`) / 上课地点;同一课程重复出现在覆盖的多个大节行,需去重
- // 校历 #kbtable 行 td[0]=周次、td[1].title=该周周一日期
- const LIDAPOLY_TIME_SLOTS = [
- { number: 1, startTime: "08:30", endTime: "09:10" },
- { number: 2, startTime: "09:15", endTime: "09:55" },
- { number: 3, startTime: "10:05", endTime: "10:45" },
- { number: 4, startTime: "10:50", endTime: "11:30" },
- { number: 5, startTime: "12:30", endTime: "13:10" },
- { number: 6, startTime: "13:15", endTime: "13:55" },
- { number: 7, startTime: "14:05", endTime: "14:45" },
- { number: 8, startTime: "14:50", endTime: "15:30" },
- { number: 9, startTime: "15:40", endTime: "16:20" },
- { number: 10, startTime: "16:25", endTime: "17:05" },
- { number: 11, startTime: "17:15", endTime: "17:55" },
- { number: 12, startTime: "18:00", endTime: "18:40" },
- { number: 13, startTime: "18:50", endTime: "19:30" },
- { number: 14, startTime: "19:35", endTime: "20:15" },
- { number: 15, startTime: "20:25", endTime: "21:05" },
- { number: 16, startTime: "21:10", endTime: "21:50" }
- ];
- // 周次串:"1-16" / "1,3-5" → [1,3,4,5]
- function parseWeeks(text) {
- const weeks = [];
- for (const part of String(text).split(/[,,]/)) {
- const range = part.match(/^(\d+)-(\d+)$/);
- if (range) {
- for (let w = Number(range[1]); w <= Number(range[2]); w++) weeks.push(w);
- } else if (/^\d+$/.test(part)) {
- weeks.push(Number(part));
- }
- }
- return [...new Set(weeks)].sort((a, b) => a - b);
- }
- // 课名 = 详情 div 第一个 <br> 前的文本,去掉尾部 [学分][性质]
- function courseNameOf(div) {
- let text = "";
- for (const node of div.childNodes) {
- if (node.nodeName === "BR") break;
- text += node.textContent || "";
- }
- return text.trim().replace(/\s*\[[0-9.]+\]\[[^\]]+\]\s*$/, "").trim();
- }
- // 解析详情 div:周次节次取自 班级 font 内的 `1-16(全部)[01-02-03节]`
- function parseDetailDiv(div, day) {
- const classFont = div.querySelector("font[title=班级]");
- if (!classFont) return null;
- const info = (classFont.textContent || "").replace(/\s+/g, "");
- const matched = info.match(/([\d,\-]+)\(([^)]*)\)\[(\d[\d\-]*)节\]$/);
- if (!matched) return null;
- const isOdd = matched[2].includes("单");
- const isEven = matched[2].includes("双");
- const weeks = parseWeeks(matched[1])
- .filter(w => (!isOdd || w % 2 === 1) && (!isEven || w % 2 === 0));
- if (weeks.length === 0) return null;
- const nums = matched[3].split("-").map(Number);
- const startSection = Math.min(...nums);
- const endSection = Math.max(...nums);
- if (!startSection || endSection > 16) return null;
- const name = courseNameOf(div);
- if (!name) return null;
- const teacherFont = div.querySelector("font[title=老师]");
- const roomFont = div.querySelector("font[title=上课地点]");
- return {
- name: name,
- teacher: teacherFont ? teacherFont.textContent.trim() : "",
- position: roomFont ? roomFont.textContent.trim() : "",
- day: day,
- startSection: startSection,
- endSection: endSection,
- weeks: weeks
- };
- }
- // 请求课表页 HTML(不带参数时为系统默认学期)
- async function fetchScheduleDoc(semesterId) {
- let url = "/shldzyjsxy_jsxsd/xskb/xskb_list.do";
- if (semesterId) url += "?xnxq01id=" + encodeURIComponent(semesterId);
- const resp = await fetch(url, { method: "GET", credentials: "include" });
- if (!resp.ok) throw new Error("课表请求失败(HTTP " + resp.status + ")");
- return new DOMParser().parseFromString(await resp.text(), "text/html");
- }
- // 课表页学年学期下拉:全部选项 + 选中项
- function readSemesterOptions(doc) {
- const sel = doc.querySelector("#xnxq01id");
- if (!sel) return null;
- const options = Array.from(sel.options)
- .map(opt => String(opt.value).trim())
- .filter(value => /^\d{4}-\d{4}-\d$/.test(value));
- if (options.length === 0) return null;
- const selectedValue = sel.selectedOptions && sel.selectedOptions[0]
- ? String(sel.selectedOptions[0].value).trim() : "";
- const defaultIndex = Math.max(0, options.indexOf(selectedValue));
- return { options, defaultIndex };
- }
- // 从课表页 #kbtable 解析课程,同一课程重复出现在多个大节行需去重
- function parseCourses(doc) {
- const map = new Map();
- const table = doc.querySelector("#kbtable");
- for (const row of table.rows) {
- if (row.cells.length < 8) continue; // 表头、备注行
- for (let i = 1; i <= 7; i++) {
- const td = row.cells[i];
- if (!td) continue;
- for (const div of td.querySelectorAll("div.kbcontent")) {
- const course = parseDetailDiv(div, i);
- if (!course) continue;
- const key = [course.name, course.teacher, course.position, course.day,
- course.startSection, course.endSection, course.weeks.join(",")].join("|");
- if (!map.has(key)) map.set(key, course);
- }
- }
- }
- return Array.from(map.values()).sort((a, b) =>
- a.day - b.day || a.startSection - b.startSection || a.name.localeCompare(b.name));
- }
- // 教学周历:第 1 周周一即开学日期,有效行数即总周数
- async function fetchSemesterConfig(semesterId) {
- try {
- const resp = await fetch("/shldzyjsxy_jsxsd/jxzl/jxzl_query?Ves632DSdyV=NEW_XSD_WDZM", {
- method: "POST",
- credentials: "include",
- headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" },
- body: "xnxq01id=" + encodeURIComponent(semesterId)
- });
- if (!resp.ok) return null;
- const doc = new DOMParser().parseFromString(await resp.text(), "text/html");
- let startDate = "";
- let totalWeeks = 0;
- for (const row of doc.querySelectorAll("#kbtable tr")) {
- const cells = row.cells;
- if (!cells || cells.length < 2) continue;
- const weekNo = cells[0].textContent.trim();
- if (!/^\d+$/.test(weekNo)) continue;
- totalWeeks++;
- if (Number(weekNo) === 1) {
- const matched = (cells[1].getAttribute("title") || "").match(/(\d{4})年(\d{2})月(\d{2})/);
- if (matched) startDate = matched[1] + "-" + matched[2] + "-" + matched[3];
- }
- }
- if (!startDate || !totalWeeks) return null;
- return { semesterStartDate: startDate, semesterTotalWeeks: totalWeeks };
- } catch (e) { return null; }
- }
- async function runImportFlow() {
- const confirmed = await window.shiguangBridgePromise.showAlert(
- "教务系统课表导入",
- "导入前请确保您已在浏览器中成功登录上海立达学院教务系统",
- "好的,开始导入");
- if (!confirmed) { window.shiguangBridge.showToast("用户取消了导入。"); return; }
- try {
- window.shiguangBridge.showToast("正在请求课表数据...");
- // 第一次请求不带学期,拿默认学期的课表页(含学期下拉列表)
- const defaultDoc = await fetchScheduleDoc(null);
- const semesterOptions = readSemesterOptions(defaultDoc);
- if (!semesterOptions) throw new Error("未能读取学期列表,请确认已登录教务系统。");
- const index = await window.shiguangBridgePromise.showSingleSelection(
- "选择学期", JSON.stringify(semesterOptions.options), semesterOptions.defaultIndex);
- if (index === null || index === -1) { window.shiguangBridge.showToast("用户取消了导入。"); return; }
- const semesterId = semesterOptions.options[index];
- // 若所选学期即默认学期,直接复用第一次结果;否则带参数重新请求
- const selectedDoc = semesterId === semesterOptions.options[semesterOptions.defaultIndex]
- ? defaultDoc
- : await fetchScheduleDoc(semesterId);
- const courses = parseCourses(selectedDoc);
- if (courses.length === 0) throw new Error("未解析到课程,请确认所选学期有课。");
- const config = await fetchSemesterConfig(semesterId);
- await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
- if (config) {
- try { await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config)); } catch (e) {}
- } else {
- window.shiguangBridge.showToast("未取到开学日期,已跳过课表配置,请在应用内手动设置。");
- }
- try { await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(LIDAPOLY_TIME_SLOTS)); } catch (e) {}
- window.shiguangBridge.showToast("课程导入成功,共导入 " + courses.length + " 门课程!");
- window.shiguangBridge.notifyTaskCompletion();
- } catch (e) {
- window.shiguangBridge.showToast("导入失败:" + e.message);
- console.error("JS: Import Error", e);
- }
- }
- runImportFlow();
|