|
@@ -0,0 +1,214 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 常州大学 (CCZU) 课表适配器
|
|
|
|
|
+ * 系统: .NET ASPX 自研教务
|
|
|
|
|
+ * 课表页面: /web_jxrw/cx_kb_xsgrkb.aspx (iframe)
|
|
|
|
|
+ * 数据位置: HTML 表格 #GVxkkb
|
|
|
|
|
+ */
|
|
|
|
|
+(function () {
|
|
|
|
|
+ "use strict";
|
|
|
|
|
+
|
|
|
|
|
+ function parseWeeks(raw) {
|
|
|
|
|
+ if (!raw || raw === "/") return [];
|
|
|
|
|
+ const cleaned = raw.replace(/\/+$/, "").trim();
|
|
|
|
|
+ if (!cleaned) return [];
|
|
|
|
|
+ const weeks = [];
|
|
|
|
|
+ const parts = cleaned.split(",");
|
|
|
|
|
+ for (const part of parts) {
|
|
|
|
|
+ const trimmed = part.trim();
|
|
|
|
|
+ if (!trimmed) continue;
|
|
|
|
|
+ const rangeMatch = trimmed.match(/^(\d+)\s*[-~]\s*(\d+)$/);
|
|
|
|
|
+ if (rangeMatch) {
|
|
|
|
|
+ const start = parseInt(rangeMatch[1], 10);
|
|
|
|
|
+ const end = parseInt(rangeMatch[2], 10);
|
|
|
|
|
+ for (let w = start; w <= end; w++) weeks.push(w);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const num = parseInt(trimmed, 10);
|
|
|
|
|
+ if (!isNaN(num)) weeks.push(num);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return [...new Set(weeks)];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function parseSection(raw) {
|
|
|
|
|
+ if (!raw) return null;
|
|
|
|
|
+ const n = parseInt(raw.trim(), 10);
|
|
|
|
|
+ return isNaN(n) ? null : n;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function parseCourseCell(cellText) {
|
|
|
|
|
+ if (!cellText || cellText === "\u00a0" || cellText.trim() === "") return [];
|
|
|
|
|
+ const courses = [];
|
|
|
|
|
+ const entries = cellText.split("/");
|
|
|
|
|
+ for (const entry of entries) {
|
|
|
|
|
+ const trimmed = entry.trim();
|
|
|
|
|
+ if (!trimmed) continue;
|
|
|
|
|
+ const match = trimmed.match(
|
|
|
|
|
+ /^(.+?)\s+([A-Za-z0-9\u4e00-\u9fff]+(?:机房|阶)?)\s+([\d,\-~]+(?:,\d+[\-~]\d+)*)\s*,?\s*(.*)$/
|
|
|
|
|
+ );
|
|
|
|
|
+ if (match) {
|
|
|
|
|
+ const courseName = match[1].trim();
|
|
|
|
|
+ const room = match[2].trim();
|
|
|
|
|
+ const weeksRaw = match[3].trim();
|
|
|
|
|
+ const teacher = match[4].trim().replace(/\/+$/, "").trim() || "";
|
|
|
|
|
+ courses.push({
|
|
|
|
|
+ courseName,
|
|
|
|
|
+ room,
|
|
|
|
|
+ teacher,
|
|
|
|
|
+ weeksRaw,
|
|
|
|
|
+ weeks: parseWeeks(weeksRaw),
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const simpleMatch = trimmed.match(/^(.+?)\s+([A-Za-z0-9\u4e00-\u9fff]+(?:机房|阶)?)\s*/);
|
|
|
|
|
+ if (simpleMatch) {
|
|
|
|
|
+ courses.push({
|
|
|
|
|
+ courseName: simpleMatch[1].trim(),
|
|
|
|
|
+ room: simpleMatch[2].trim(),
|
|
|
|
|
+ teacher: "",
|
|
|
|
|
+ weeksRaw: "",
|
|
|
|
|
+ weeks: [],
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return courses;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function extractTeacherMap(doc) {
|
|
|
|
|
+ const map = {};
|
|
|
|
|
+ const target = doc || document;
|
|
|
|
|
+ const table = target.getElementById("GVxkall");
|
|
|
|
|
+ if (!table) return map;
|
|
|
|
|
+ const rows = table.querySelectorAll("tr.dg1-item");
|
|
|
|
|
+ for (const row of rows) {
|
|
|
|
|
+ const cells = row.querySelectorAll("td");
|
|
|
|
|
+ if (cells.length >= 6) {
|
|
|
|
|
+ const name = cells[1]?.textContent?.trim();
|
|
|
|
|
+ const teacher = cells[5]?.textContent?.trim();
|
|
|
|
|
+ if (name && teacher && teacher !== "\u00a0") {
|
|
|
|
|
+ map[name] = teacher;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function parseSemester(raw) {
|
|
|
|
|
+ if (!raw) return null;
|
|
|
|
|
+ const m = raw.trim().match(/^(\d{2})-(\d{2})-(\d)$/);
|
|
|
|
|
+ if (!m) return null;
|
|
|
|
|
+ const startYear = 2000 + parseInt(m[1], 10);
|
|
|
|
|
+ const endYear = 2000 + parseInt(m[2], 10);
|
|
|
|
|
+ const semester = parseInt(m[3], 10);
|
|
|
|
|
+ return { startYear, endYear, semester };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function findTable() {
|
|
|
|
|
+ let table = document.getElementById("GVxkkb");
|
|
|
|
|
+ if (table) return { doc: document, table };
|
|
|
|
|
+
|
|
|
|
|
+ const iframes = document.querySelectorAll("iframe");
|
|
|
|
|
+ for (const iframe of iframes) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
|
|
|
|
+ if (!iframeDoc) continue;
|
|
|
|
|
+ table = iframeDoc.getElementById("GVxkkb");
|
|
|
|
|
+ if (table) return { doc: iframeDoc, table };
|
|
|
|
|
+
|
|
|
|
|
+ const innerIframes = iframeDoc.querySelectorAll("iframe");
|
|
|
|
|
+ for (const inner of innerIframes) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const innerDoc = inner.contentDocument || inner.contentWindow.document;
|
|
|
|
|
+ if (!innerDoc) continue;
|
|
|
|
|
+ table = innerDoc.getElementById("GVxkkb");
|
|
|
|
|
+ if (table) return { doc: innerDoc, table };
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const TimeSlots = [
|
|
|
|
|
+ { number: 1, startTime: "08:00", endTime: "08:40" },
|
|
|
|
|
+ { number: 2, startTime: "08:45", endTime: "09:25" },
|
|
|
|
|
+ { number: 3, startTime: "09:45", endTime: "10:25" },
|
|
|
|
|
+ { number: 4, startTime: "10:35", endTime: "11:15" },
|
|
|
|
|
+ { number: 5, startTime: "11:20", endTime: "12:00" },
|
|
|
|
|
+ { number: 6, startTime: "13:30", endTime: "14:10" },
|
|
|
|
|
+ { number: 7, startTime: "14:15", endTime: "14:55" },
|
|
|
|
|
+ { number: 8, startTime: "15:15", endTime: "15:55" },
|
|
|
|
|
+ { number: 9, startTime: "16:00", endTime: "16:40" },
|
|
|
|
|
+ { number: 10, startTime: "18:30", endTime: "19:10" },
|
|
|
|
|
+ { number: 11, startTime: "19:15", endTime: "19:55" },
|
|
|
|
|
+ { number: 12, startTime: "20:05", endTime: "20:45" }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ async function importPresetTimeSlots(timeSlots) {
|
|
|
|
|
+ if (timeSlots.length === 0) return;
|
|
|
|
|
+ try { await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots)); } catch (e) {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function runImportFlow() {
|
|
|
|
|
+ const result = findTable();
|
|
|
|
|
+ if (!result) {
|
|
|
|
|
+ window.shiguangBridge.showToast("找不到课表表格,请确保已进入课表页面");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const { doc, table } = result;
|
|
|
|
|
+
|
|
|
|
|
+ const teacherMap = extractTeacherMap(doc);
|
|
|
|
|
+ const courses = [];
|
|
|
|
|
+ const rows = table.querySelectorAll("tr");
|
|
|
|
|
+
|
|
|
|
|
+ const semesterSelect = doc.getElementById("DDxq");
|
|
|
|
|
+ const semesterRaw = semesterSelect?.value || "";
|
|
|
|
|
+ const semester = parseSemester(semesterRaw);
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 1; i < rows.length; i++) {
|
|
|
|
|
+ const cells = rows[i].querySelectorAll("td");
|
|
|
|
|
+ if (cells.length < 8) continue;
|
|
|
|
|
+
|
|
|
|
|
+ const section = parseSection(cells[0]?.textContent);
|
|
|
|
|
+ if (section === null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ for (let day = 1; day <= 7; day++) {
|
|
|
|
|
+ const cellText = cells[day]?.textContent?.trim() || "";
|
|
|
|
|
+ if (!cellText || cellText === "\u00a0") continue;
|
|
|
|
|
+
|
|
|
|
|
+ const parsed = parseCourseCell(cellText);
|
|
|
|
|
+ for (const c of parsed) {
|
|
|
|
|
+ const mergedTeacher = c.teacher || teacherMap[c.courseName] || "";
|
|
|
|
|
+ courses.push({
|
|
|
|
|
+ name: c.courseName,
|
|
|
|
|
+ teacher: mergedTeacher,
|
|
|
|
|
+ position: c.room,
|
|
|
|
|
+ day: day,
|
|
|
|
|
+ startSection: section,
|
|
|
|
|
+ endSection: section + 1,
|
|
|
|
|
+ weeks: c.weeks,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (courses.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("未找到课程数据");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
|
|
+ await importPresetTimeSlots(TimeSlots);
|
|
|
|
|
+ window.shiguangBridge.showToast(`课程导入成功,共 ${courses.length} 门课程!`);
|
|
|
|
|
+ window.shiguangBridge.notifyTaskCompletion();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.shiguangBridge.showToast("课程保存失败: " + error.message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (document.readyState === "loading") {
|
|
|
|
|
+ document.addEventListener("DOMContentLoaded", runImportFlow);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ runImportFlow();
|
|
|
|
|
+ }
|
|
|
|
|
+})();
|