Xu Kai Rui 2 недель назад
Родитель
Сommit
c52f42e0c5
3 измененных файлов с 250 добавлено и 0 удалено
  1. 5 0
      index/root_index.yaml
  2. 11 0
      resources/ZZU/adapters.yaml
  3. 234 0
      resources/ZZU/zzu.js

+ 5 - 0
index/root_index.yaml

@@ -817,4 +817,9 @@ schools:
     name: "西南大学"
     initial: "X"
     resource_folder: "SWU"
+
+  - id: "ZZU"
+    name: "郑州大学"
+    initial: "Z"
+    resource_folder: "ZZU"
     

+ 11 - 0
resources/ZZU/adapters.yaml

@@ -0,0 +1,11 @@
+# resources/ZZU/adapters.yaml
+# 郑州大学 拾光课程表适配
+adapters:
+  - adapter_id: "ZZU_01"
+    adapter_name: "郑州大学"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "zzu.js"
+    import_url: "https://cas.s.zzu.edu.cn/cas/a/login?service=https%3A%2F%2Fjwxt.zzu.edu.cn%2Fstudent%2Fsso%2Flogin"
+    maintainer: "LilyCarry"
+    description: "登录之后向右下角滑,点击课表.课表出现后点击右下角导入即可."
+

+ 234 - 0
resources/ZZU/zzu.js

@@ -0,0 +1,234 @@
+// 郑州大学 拾光课程表适配脚本
+// 适配系统:树维新一代智慧教务系统(jwxt.zzu.edu.cn)
+
+(async function () {
+    // 郑州大学标准 12 节作息时间(第 1-4 节上午,第 5-8 节下午,第 9-12 节晚上)
+    const presetTimeSlots = [
+        { number: 1, startTime: "08:00", endTime: "08:45" },
+        { number: 2, startTime: "08:55", endTime: "09:40" },
+        { number: 3, startTime: "10:10", endTime: "10:55" },
+        { number: 4, startTime: "11:05", endTime: "11:50" },
+        { number: 5, startTime: "14:00", endTime: "14:45" },
+        { number: 6, startTime: "14:55", endTime: "15:40" },
+        { number: 7, startTime: "16:10", endTime: "16:55" },
+        { number: 8, startTime: "17:05", endTime: "17:50" },
+        { number: 9, startTime: "19:00", endTime: "19:45" },
+        { number: 10, startTime: "19:55", endTime: "20:40" },
+        { number: 11, startTime: "20:50", endTime: "21:35" },
+        { number: 12, startTime: "21:40", endTime: "22:25" }
+    ];
+
+    function showToast(message) {
+        if (window.shiguangBridge && window.shiguangBridge.showToast) {
+            window.shiguangBridge.showToast(message);
+        }
+    }
+
+    /**
+     * 获取学期下拉列表
+     */
+    async function fetchSemesters() {
+        try {
+            const res = await fetch("/student/for-std/course-table", {
+                headers: {
+                    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+                    "x-requested-with": "XMLHttpRequest"
+                },
+                method: "GET",
+                credentials: "include"
+            });
+            if (!res.ok) return null;
+
+            const htmlText = await res.text();
+            const parser = new DOMParser();
+            const doc = parser.parseFromString(htmlText, "text/html");
+            const select = doc.getElementById("allSemesters");
+            if (!select) return null;
+
+            const options = Array.from(select.querySelectorAll("option")).map(opt => ({
+                label: opt.textContent.trim(),
+                value: opt.value.trim(),
+                selected: opt.hasAttribute("selected") || opt.selected
+            })).filter(o => o.value && o.label);
+
+            return options;
+        } catch (e) {
+            return null;
+        }
+    }
+
+    /**
+     * 获取学期开学日期与结束日期
+     */
+    async function fetchSemesterMetadata(semesterId) {
+        try {
+            const res = await fetch(`/student/ws/semester/get/${semesterId}`, {
+                headers: {
+                    "accept": "*/*",
+                    "x-requested-with": "XMLHttpRequest"
+                },
+                method: "GET",
+                credentials: "include"
+            });
+            if (!res.ok) return null;
+            const data = await res.json();
+            return {
+                startDate: data.startDate || null,
+                endDate: data.endDate || null
+            };
+        } catch (e) {
+            return null;
+        }
+    }
+
+    /**
+     * 获取并解析课程表数据
+     */
+    async function fetchAndParseCourses(semesterId) {
+        try {
+            const url = `/student/for-std/course-table/semester/${semesterId}/print-data?semesterId=${semesterId}&hasExperiment=true`;
+            const res = await fetch(url, {
+                headers: {
+                    "accept": "*/*",
+                    "x-requested-with": "XMLHttpRequest"
+                },
+                method: "GET",
+                credentials: "include"
+            });
+
+            if (!res.ok) return null;
+            const data = await res.json();
+            if (!data) return null;
+
+            const rawActivities = (data.studentTableVms && data.studentTableVms[0] ? data.studentTableVms[0].activities : (data.studentTableVm ? data.studentTableVm.activities : (data.activities || []))) || [];
+            if (!Array.isArray(rawActivities) || rawActivities.length === 0) return null;
+
+            const parsedCourses = [];
+            for (const act of rawActivities) {
+                if (!act.courseName || !act.weekday || !act.startUnit || !act.endUnit || !Array.isArray(act.weekIndexes)) {
+                    continue;
+                }
+
+                const teacherName = Array.isArray(act.teachers) && act.teachers.length > 0
+                    ? act.teachers.map(t => String(t).replace(/\(\d+\)/g, "").replace(/\[\d+\]/g, "").trim()).filter(Boolean).join(",")
+                    : (typeof act.teachers === "string" ? act.teachers.replace(/\(\d+\)/g, "").trim() : "");
+
+                const weeks = act.weekIndexes.map(Number).filter(w => Number.isInteger(w) && w > 0).sort((a, b) => a - b);
+                if (weeks.length === 0) continue;
+
+                const startSection = Number(act.startUnit);
+                const endSection = Number(act.endUnit);
+                const sections = [];
+                for (let s = startSection; s <= endSection; s++) sections.push(s);
+
+                parsedCourses.push({
+                    name: String(act.courseName).trim(),
+                    teacher: teacherName,
+                    position: String(act.room || act.building || "未知地点").trim(),
+                    day: Number(act.weekday),
+                    startSection: startSection,
+                    endSection: endSection,
+                    sections: sections,
+                    weeks: weeks
+                });
+            }
+
+            return parsedCourses.length > 0 ? parsedCourses : null;
+        } catch (e) {
+            return null;
+        }
+    }
+
+    /**
+     * 计算学期总周数
+     */
+    function calculateTotalWeeks(startDate, endDate) {
+        if (!startDate || !endDate) return 20;
+        const start = new Date(startDate);
+        const end = new Date(endDate);
+        const diffMs = end.getTime() - start.getTime();
+        if (diffMs <= 0) return 20;
+        const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
+        return Math.min(Math.max(Math.ceil(diffDays / 7), 16), 30);
+    }
+
+    /**
+     * 流程主入口
+     */
+    async function runImportFlow() {
+        showToast("正在拉取学期列表...");
+
+        const semesters = await fetchSemesters();
+        if (!semesters || semesters.length === 0) {
+            showToast("获取学期列表失败,请确认已进入教务课表页面");
+            return;
+        }
+
+        const labels = semesters.map(s => s.label);
+        let defaultIndex = semesters.findIndex(s => s.selected);
+        if (defaultIndex < 0) defaultIndex = 0;
+
+        const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
+            "选择学期",
+            JSON.stringify(labels),
+            defaultIndex
+        );
+
+        if (selectedIndex === null || selectedIndex < 0 || selectedIndex >= semesters.length) {
+            showToast("操作已取消");
+            return;
+        }
+
+        const selectedSemester = semesters[selectedIndex];
+        showToast("正在拉取课表数据...");
+
+        const [meta, courses] = await Promise.all([
+            fetchSemesterMetadata(selectedSemester.value),
+            fetchAndParseCourses(selectedSemester.value)
+        ]);
+
+        if (!courses || courses.length === 0) {
+            showToast("未查询到有效课程数据");
+            return;
+        }
+
+        let totalWeeks = 20;
+        if (meta && meta.startDate && meta.endDate) {
+            totalWeeks = calculateTotalWeeks(meta.startDate, meta.endDate);
+        } else {
+            const allWeeks = courses.flatMap(c => c.weeks || []);
+            if (allWeeks.length > 0) totalWeeks = Math.max(...allWeeks);
+        }
+
+        const configData = {
+            semesterStartDate: meta && meta.startDate ? meta.startDate : "",
+            semesterTotalWeeks: Math.max(totalWeeks, 18),
+            firstDayOfWeek: 1,
+            defaultClassDuration: 45,
+            defaultBreakDuration: 10
+        };
+
+        if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveCourseConfig) {
+            await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(configData));
+        }
+
+        if (window.shiguangBridgePromise && window.shiguangBridgePromise.savePresetTimeSlots) {
+            await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
+        }
+
+        if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveImportedCourses) {
+            const saveOk = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
+            if (!saveOk) {
+                showToast("课程保存失败");
+                return;
+            }
+        }
+
+        showToast(`成功导入 ${courses.length} 门课程及作息时间!`);
+        if (window.shiguangBridge && window.shiguangBridge.notifyTaskCompletion) {
+            window.shiguangBridge.notifyTaskCompletion();
+        }
+    }
+
+    runImportFlow();
+})();