Ver Fonte

add: 适配扬州大学URP教务系统 (#446)

Thanatos há 1 mês atrás
pai
commit
18a8a40262
3 ficheiros alterados com 175 adições e 1 exclusões
  1. 5 1
      index/root_index.yaml
  2. 9 0
      resources/YZU/adapters.yaml
  3. 161 0
      resources/YZU/yzu.js

+ 5 - 1
index/root_index.yaml

@@ -702,4 +702,8 @@ schools:
     name: "上海财经大学浙江学院"
     initial: "S"
     resource_folder: "SHUFEZJ"
-
+    
+  - id: "YZU"
+    name: "扬州大学"
+    initial: "Y"
+    resource_folder: "YZU"   

+ 9 - 0
resources/YZU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/YZU/adapters.yaml
+adapters:
+  - adapter_id: "YZU_01"
+    adapter_name: "扬州大学URP教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "yzu.js"
+    import_url: "https://webvpn.yzu.edu.cn/login"
+    maintainer: "Thanatos"
+    description: "扬州大学URP教务适配,通过网瑞达WebVPN访问,登录后需进入PC端教务系统「选课管理」页面,选择历年学期课表,再点击导入."

+ 161 - 0
resources/YZU/yzu.js

@@ -0,0 +1,161 @@
+// 扬州大学(yzu.edu.cn) 拾光课程表适配脚本
+// 基于URP教务系统接口适配
+
+const PAGE_URL = window.location.href;
+const VPN_BASE = (PAGE_URL.match(/^(https?:\/\/[^/]+\/http[^/]*\/[^/]+)/) || [])[1];
+if (!VPN_BASE) {
+    AndroidBridge.showToast("请通过扬州大学 WebVPN 进入教务系统后使用");
+    throw new Error("无法定位 WebVPN 基址");
+}
+
+const API_PATH = "/student/courseSelect/thisSemesterCurriculum/ajaxStudentSchedule/callback";
+
+function parseWeeks(s) {
+    const w = [];
+    if (!s) return w;
+    for (let i = 0; i < s.length; i++) if (s[i] === '1') w.push(i + 1);
+    return w;
+}
+
+function cleanTeacher(t) {
+    return (t || "").replace(/\*/g, "").trim();
+}
+
+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;
+        const keys = Object.keys(map);
+        for (let j = 0; j < keys.length; j++) {
+            const c = map[keys[j]];
+            if (!c || !c.courseName || !c.timeAndPlaceList) continue;
+            const teacher = cleanTeacher(c.attendClassTeacher);
+            const tpl = c.timeAndPlaceList;
+            for (let k = 0; k < tpl.length; k++) {
+                const tp = tpl[k];
+                const start = tp.classSessions;
+                const weeks = parseWeeks(tp.classWeek);
+                if (!start || !tp.classDay || weeks.length === 0) continue;
+                const key = c.courseName + '|' + teacher + '|' + tp.classDay + '|' + start + '|' + (tp.classSessions + tp.continuingSession - 1) + '|' + weeks.join(',');
+                if (seen.has(key)) continue;
+                seen.add(key);
+                courses.push({
+                    name: c.courseName,
+                    teacher: teacher,
+                    position: ((tp.campusName || "") + (tp.classroomName || "")).trim() || "待定",
+                    day: tp.classDay,
+                    startSection: start,
+                    endSection: tp.classSessions + tp.continuingSession - 1,
+                    weeks: weeks,
+                    isCustomTime: false
+                });
+            }
+        }
+    }
+    return courses;
+}
+
+function parseTimeSlots(data) {
+    const slots = [];
+    const list = (data && data.jcsjbs) || [];
+    for (let i = 0; i < list.length; i++) {
+        const it = list[i];
+        const n = parseInt(it.jc, 10);
+        const st = it.kssj, et = it.jssj;
+        if (isNaN(n) || !st || st.length !== 4 || !et || et.length !== 4) continue;
+        slots.push({
+            number: n,
+            startTime: st.substring(0, 2) + ":" + st.substring(2),
+            endTime: et.substring(0, 2) + ":" + et.substring(2)
+        });
+    }
+    return slots.sort((a, b) => a.number - b.number);
+}
+
+const FALLBACK_TIME_SLOTS = [
+    { "number": 1, "startTime": "08:00", "endTime": "08:45" },
+    { "number": 2, "startTime": "08:55", "endTime": "09:40" },
+    { "number": 3, "startTime": "09:55", "endTime": "10:40" },
+    { "number": 4, "startTime": "10:50", "endTime": "11:35" },
+    { "number": 5, "startTime": "11:45", "endTime": "12:30" },
+    { "number": 6, "startTime": "14:30", "endTime": "15:15" },
+    { "number": 7, "startTime": "15:25", "endTime": "16:10" },
+    { "number": 8, "startTime": "16:20", "endTime": "17:05" },
+    { "number": 9, "startTime": "17:15", "endTime": "18:00" },
+    { "number": 10, "startTime": "18:10", "endTime": "18:55" },
+    { "number": 11, "startTime": "19:30", "endTime": "20:15" },
+    { "number": 12, "startTime": "20:25", "endTime": "21:10" }
+];
+
+async function runImportFlow() {
+    try {
+        const confirmed = await window.AndroidBridgePromise.showAlert(
+            "扬州大学课表导入",
+            "请确认:\n1. 已登录扬州大学 WebVPN(webvpn.yzu.edu.cn)\n2. 已进入教务「我的课表」页面(能看到学期下拉框)\n未在课表页会导致获取失败。",
+            "好的,开始导入"
+        );
+        if (!confirmed) return;
+
+        const select = document.getElementById("planCode");
+        if (!select || select.options.length === 0) {
+            AndroidBridge.showToast("未找到学期选择框,请先进入课表页面");
+            return;
+        }
+        const texts = [];
+        const values = [];
+        for (let i = 0; i < select.options.length; i++) {
+            texts.push(select.options[i].text);
+            values.push(select.options[i].value);
+        }
+        const defaultIndex = select.selectedIndex >= 0 ? select.selectedIndex : 0;
+        const idx = await window.AndroidBridgePromise.showSingleSelection(
+            "选择学期",
+            JSON.stringify(texts),
+            defaultIndex
+        );
+        if (idx === null || idx < 0) {
+            AndroidBridge.showToast("导入已取消");
+            return;
+        }
+
+        AndroidBridge.showToast("正在获取教务数据...");
+        const res = await fetch(VPN_BASE + API_PATH, {
+            "headers": {
+                "Accept": "application/json, text/javascript, */*; q=0.01",
+                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
+                "X-Requested-With": "XMLHttpRequest"
+            },
+            "body": "planCode=" + values[idx],
+            "method": "POST",
+            "credentials": "include"
+        });
+        if (!res.ok) throw new Error("网络请求失败,状态码: " + res.status);
+
+        const data = await res.json();
+        const courses = parseCourses(data);
+        if (courses.length === 0) {
+            AndroidBridge.showToast("未解析到课程数据,请确认所选学期有课");
+            return;
+        }
+
+        const timeSlots = parseTimeSlots(data);
+        const ok = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (!ok) {
+            AndroidBridge.showToast("课程保存失败");
+            return;
+        }
+        await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots.length ? timeSlots : FALLBACK_TIME_SLOTS));
+        await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({ semesterTotalWeeks: 20 }));
+
+        AndroidBridge.showToast("成功导入 " + courses.length + " 个课程时段");
+        AndroidBridge.notifyTaskCompletion();
+    } catch (e) {
+        console.error("导入异常:", e);
+        AndroidBridge.showToast("导入失败: " + e.message);
+    }
+}
+
+runImportFlow();