Procházet zdrojové kódy

add: 辽宁大学教务适配

739C1AE2 před 3 týdny
rodič
revize
94c2bea3df
3 změnil soubory, kde provedl 345 přidání a 1 odebrání
  1. 6 1
      index/root_index.yaml
  2. 330 0
      resources/LNU/LNU_01.js
  3. 9 0
      resources/LNU/adapters.yaml

+ 6 - 1
index/root_index.yaml

@@ -766,4 +766,9 @@ schools:
   - id: "ZZVCAE"
     name: "郑州汽车工程职业学院"
     initial: "Z"
-    resource_folder: "ZZVCAE"
+    resource_folder: "ZZVCAE"
+
+  - id: "LNU"
+    name: "辽宁大学"
+    initial: "L"
+    resource_folder: "LNU"

+ 330 - 0
resources/LNU/LNU_01.js

@@ -0,0 +1,330 @@
+// 辽宁大学程表适配脚本
+// 基于 URP 教务系统 JSON 接口适配
+
+// 解析 classWeek 二进制串,如 "111111111111111100000000" -> [1..16]
+function parseWeeks(binaryStr) {
+    const weeks = [];
+    if (!binaryStr) return weeks;
+    for (let i = 0; i < binaryStr.length; i++) {
+        if (binaryStr[i] === '1') weeks.push(i + 1);
+    }
+    return weeks;
+}
+
+function cleanTeacher(teacher) {
+    return (teacher || "").replace(/\*/g, " ").replace(/\s+/g, " ").trim();
+}
+
+function formatTime(timeStr) {
+    if (timeStr && timeStr.length === 4) {
+        return timeStr.substring(0, 2) + ":" + timeStr.substring(2);
+    }
+    return timeStr || "";
+}
+
+function buildPosition(campus, building, room) {
+    const campusPart = (campus || "").trim();
+    let buildingPart = (building || "").trim();
+    let roomPart = (room || "").trim();
+    if (campusPart && buildingPart.startsWith(campusPart)) {
+        buildingPart = buildingPart.slice(campusPart.length);
+    }
+    if (campusPart && roomPart.startsWith(campusPart)) {
+        roomPart = roomPart.slice(campusPart.length);
+    }
+    const result = (campusPart + buildingPart + roomPart).trim();
+    return result || "待定";
+}
+
+function parseCourses(data) {
+    const courses = [];
+    const seen = new Set();
+    const list = (data && data.xkxx) || [];
+    for (let i = 0; i < list.length; i++) {
+        const map = list[i];
+        if (!map) continue;
+        for (const key in map) {
+            const course = map[key];
+            if (!course || !course.courseName || !course.timeAndPlaceList) continue;
+            const teacher = cleanTeacher(course.attendClassTeacher);
+            for (const timePlace of course.timeAndPlaceList) {
+                const day = parseInt(timePlace.classDay);
+                const start = parseInt(timePlace.classSessions);
+                const continuing = parseInt(timePlace.continuingSession) || 2;
+                const end = start + continuing - 1;
+                const weeks = parseWeeks(timePlace.classWeek);
+                if (!day || !start || weeks.length === 0) continue;
+                const position = buildPosition(timePlace.campusName, timePlace.teachingBuildingName, timePlace.classroomName);
+                const dedupKey = course.courseName + '|' + teacher + '|' + day + '|' + start + '|' + end + '|' + weeks.join(',') + '|' + position;
+                if (seen.has(dedupKey)) continue;
+                seen.add(dedupKey);
+                courses.push({
+                    name: course.courseName,
+                    teacher: teacher,
+                    position: position,
+                    day: day,
+                    startSection: start,
+                    endSection: end,
+                    weeks: weeks,
+                    isCustomTime: false
+                });
+            }
+        }
+    }
+    return courses;
+}
+
+function parseTimeSlots(data) {
+    const slots = [];
+    const list = (data && data.jcsjbs) || [];
+    for (const slot of list) {
+        const num = parseInt(slot.jc, 10);
+        if (isNaN(num)) continue;
+        slots.push({
+            number: num,
+            startTime: formatTime(slot.kssj),
+            endTime: formatTime(slot.jssj)
+        });
+    }
+    return slots.sort((a, b) => a.number - b.number);
+}
+
+// 教务系统提供的时间可能把校区搞混,所以提供各校区的作息时间表,供用户选择
+const CHONGSHAN_TIME_SLOTS = [
+    { number: 1, startTime: "08:00", endTime: "08:50" },
+    { number: 2, startTime: "09:00", endTime: "09:50" },
+    { number: 3, startTime: "10:10", endTime: "11:00" },
+    { number: 4, startTime: "11:10", endTime: "12:00" },
+    { number: 5, startTime: "13:30", endTime: "14:20" },
+    { number: 6, startTime: "14:30", endTime: "15:20" },
+    { number: 7, startTime: "15:40", endTime: "16:30" },
+    { number: 8, startTime: "16:40", endTime: "17:30" },
+    { number: 9, startTime: "18:00", endTime: "18:50" },
+    { number: 10, startTime: "19:00", endTime: "19:50" },
+    { number: 11, startTime: "20:10", endTime: "21:00" },
+    { number: 12, startTime: "21:10", endTime: "22:00" }
+];
+
+const PUHE_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: "13:30", endTime: "14:15" },
+    { number: 6, startTime: "14:20", endTime: "15:05" },
+    { number: 7, startTime: "15:25", endTime: "16:10" },
+    { number: 8, startTime: "16:15", endTime: "17:00" },
+    { number: 9, startTime: "17:30", endTime: "18:15" },
+    { number: 10, startTime: "18:20", endTime: "19:05" },
+    { number: 11, startTime: "19:25", endTime: "20:10" },
+    { number: 12, startTime: "20:15", endTime: "21:00" }
+];
+
+const WUSHENG_TIME_SLOTS = [
+    { number: 1, startTime: "08:00", endTime: "08:50" },
+    { number: 2, startTime: "09:00", endTime: "09:50" },
+    { number: 3, startTime: "10:10", endTime: "11:00" },
+    { number: 4, startTime: "11:10", endTime: "12:00" },
+    { number: 5, startTime: "13:30", endTime: "14:20" },
+    { number: 6, startTime: "14:30", endTime: "15:20" },
+    { number: 7, startTime: "15:30", endTime: "16:20" },
+    { number: 8, startTime: "16:30", endTime: "17:20" },
+    { number: 9, startTime: "18:00", endTime: "18:50" },
+    { number: 10, startTime: "19:00", endTime: "19:50" },
+    { number: 11, startTime: "20:10", endTime: "21:00" },
+    { number: 12, startTime: "21:10", endTime: "22:00" }
+];
+
+const CAMPUS_CHONGSHAN = "崇山";
+const CAMPUS_PUHE = "蒲河";
+const CAMPUS_WUSHENG = "武圣";
+
+const TIME_SLOT_OPTIONS = [
+    "从教务提取",
+    "内置" + CAMPUS_CHONGSHAN + "校区作息",
+    "内置" + CAMPUS_PUHE + "校区作息",
+    "内置" + CAMPUS_WUSHENG + "校区作息"
+];
+
+const TIME_SLOT_OPTION_EXTRACT = 0;
+const TIME_SLOT_OPTION_CHONGSHAN = 1;
+const TIME_SLOT_OPTION_PUHE = 2;
+const TIME_SLOT_OPTION_WUSHENG = 3;
+
+// 根据课程数据的 campusName 字段猜测所在校区
+function guessCampus(data) {
+    const counts = {};
+    const list = (data && data.xkxx) || [];
+    for (const map of list) {
+        if (!map) continue;
+        for (const key in map) {
+            const course = map[key];
+            if (!course || !course.timeAndPlaceList) continue;
+            for (const timePlace of course.timeAndPlaceList) {
+                const campus = (timePlace.campusName || "").trim();
+                if (campus) {
+                    counts[campus] = (counts[campus] || 0) + 1;
+                }
+            }
+        }
+    }
+    let best = null;
+    let bestCount = 0;
+    for (const name in counts) {
+        if (counts[name] > bestCount) {
+            best = name;
+            bestCount = counts[name];
+        }
+    }
+    if (best && best.includes(CAMPUS_CHONGSHAN)) {
+        return CAMPUS_CHONGSHAN; 
+    }
+    if (best && best.includes(CAMPUS_PUHE)) { 
+        return CAMPUS_PUHE; 
+    }
+    if (best && best.includes(CAMPUS_WUSHENG)) {
+        return CAMPUS_WUSHENG;
+    }
+    return null;
+}
+
+function computeTotalWeeks(courses) {
+    let max = 0;
+    for (const course of courses) {
+        for (const week of course.weeks) {
+            if (week > max) {
+                max = week;
+            }
+        }
+    }
+    return max > 0 ? max : 20;
+}
+
+function readSemesterOptions() {
+    const select = document.getElementById('planCode');
+    if (!select || !select.options || select.options.length === 0) {
+        return null;
+    }
+    const texts = [];
+    const values = [];
+    for (let i = 0; i < select.options.length; i++) {
+        texts.push(select.options[i].text.replace(/\s+/g, ' ').trim());
+        values.push(select.options[i].value);
+    }
+    return {
+        texts: texts,
+        values: values,
+        defaultIndex: select.selectedIndex >= 0 ? select.selectedIndex : 0
+    };
+}
+
+async function runImportFlow() {
+    try {
+        let method = "GET";
+        let body = undefined;
+        let idx = -1;
+        let options = null;
+
+        const apiPath = "/student/courseSelect/thisSemesterCurriculum/ajaxStudentSchedule/callback";
+        const headers = {
+            "Accept": "*/*",
+            "X-Requested-With": "XMLHttpRequest"
+        };
+
+        const confirmed = await window.shiguangBridgePromise.showAlert(
+            "辽宁大学课表导入",
+            "请确认已登录教务系统,并处于课表页面。",
+            "确定"
+        );
+        if (!confirmed) return;
+
+        options = readSemesterOptions();
+        if (options) {
+            idx = await window.shiguangBridgePromise.showSingleSelection(
+                "选择学期",
+                JSON.stringify(options.texts),
+                options.defaultIndex
+            );
+            if (idx === null || idx < 0) {
+                window.shiguangBridge.showToast("导入已取消");
+                return;
+            }
+        }
+
+        if (options && idx >= 0) {
+            method = "POST";
+            headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8";
+            headers["Accept"] = "application/json, text/javascript, */*; q=0.01";
+            body = "planCode=" + encodeURIComponent(options.values[idx]);
+        }
+
+        window.shiguangBridge.showToast("正在获取教务数据...");
+        const res = await fetch(apiPath, {
+            method: method,
+            headers: headers,
+            body: body,
+            credentials: "include"
+        });
+        if (!res.ok) {
+            window.shiguangBridge.showToast("网络请求失败,状态码: " + res.status);
+            return;
+        }
+
+        const data = await res.json();
+
+        const courses = parseCourses(data);
+        if (courses.length === 0) {
+            window.shiguangBridge.showToast("导入失败: 未解析到课程数据,请确认所选学期有课");
+            return;
+        }
+        const totalWeeks = computeTotalWeeks(courses);
+
+        const campus = guessCampus(data);
+        let defaultIndex = TIME_SLOT_OPTION_EXTRACT;
+        if (campus === CAMPUS_CHONGSHAN) {
+            defaultIndex = TIME_SLOT_OPTION_CHONGSHAN;
+        } else if (campus === CAMPUS_PUHE) {
+            defaultIndex = TIME_SLOT_OPTION_PUHE;
+        } else if (campus === CAMPUS_WUSHENG) {
+            defaultIndex = TIME_SLOT_OPTION_WUSHENG;
+        }
+        const choice = await window.shiguangBridgePromise.showSingleSelection(
+            "作息时间来源",
+            JSON.stringify(TIME_SLOT_OPTIONS),
+            defaultIndex
+        );
+        if (choice === null || choice < 0) {
+            window.shiguangBridge.showToast("导入已取消");
+            return;
+        }
+        let timeSlots;
+        if (choice === TIME_SLOT_OPTION_CHONGSHAN) {
+            timeSlots = CHONGSHAN_TIME_SLOTS;
+        } else if (choice === TIME_SLOT_OPTION_PUHE) {
+            timeSlots = PUHE_TIME_SLOTS;
+        } else if (choice === TIME_SLOT_OPTION_WUSHENG) {
+            timeSlots = WUSHENG_TIME_SLOTS;
+        } else {
+            timeSlots = parseTimeSlots(data);
+        }
+
+        const saved = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (!saved) {
+            window.shiguangBridge.showToast("课程保存失败");
+            return;
+        }
+        try {
+            await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+            await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({ semesterTotalWeeks: totalWeeks }));
+        } catch (error) {
+            window.shiguangBridge.showToast("作息时间或配置保存失败: " + error.message);
+        }
+
+        window.shiguangBridge.showToast("成功导入 " + courses.length + " 个课程时段");
+        window.shiguangBridge.notifyTaskCompletion();
+    } catch (e) {
+        window.shiguangBridge.showToast("导入失败: " + e.message);
+    }
+}
+
+runImportFlow();

+ 9 - 0
resources/LNU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/LNU/adapters.yaml
+adapters:
+  - adapter_id: "LNU_01"
+    adapter_name: "辽宁大学URP教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "LNU_01.js"
+    import_url: "http://jwstudent.lnu.edu.cn/"
+    maintainer: "739C1AE2"
+    description: "辽宁大学URP教务适配"