Browse Source

Add: 中国石油大学(北京)本科教务系统适配

严嘉哲 2 weeks ago
parent
commit
ed9cbd8955
3 changed files with 285 additions and 2 deletions
  1. 7 2
      index/root_index.yaml
  2. 9 0
      resources/CUP/adapters.yaml
  3. 269 0
      resources/CUP/cup_01.js

+ 7 - 2
index/root_index.yaml

@@ -206,7 +206,7 @@ schools:
   - id: "AHSZU"
     name: "宿州学院"
     initial: "S"
-    resource_folder: "AHSZU"    
+    resource_folder: "AHSZU"
 
   - id: "XAUT"
     name: "西安理工大学"
@@ -216,4 +216,9 @@ schools:
   - id: "HUAT"
     name: "湖北汽车工业学院"
     initial: "H"
-    resource_folder: "HUAT" 
+    resource_folder: "HUAT"
+
+  - id: "CUP"
+    name: "中国石油大学(北京)"
+    initial: "Z"
+    resource_folder: "CUP"

+ 9 - 0
resources/CUP/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/CUP/adapters.yaml
+adapters:
+  - adapter_id: "CUP_01"
+    adapter_name: "中国石油大学(北京)本科教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "cup_01.js"
+    import_url: "https://bk.cup.edu.cn/student/"
+    maintainer: "larryyan"
+    description: "点击统一身份认证登录,\n若导入学期错误, 请打开课表页面选择后重新导入。"

+ 269 - 0
resources/CUP/cup_01.js

@@ -0,0 +1,269 @@
+// 文件: school.js
+
+// 1. 显示一个公告信息弹窗
+async function promptUserToStart() {
+    try {
+        console.log("即将显示公告弹窗...");
+        const confirmed = await window.AndroidBridgePromise.showAlert(
+            "重要通知",
+            "导入前请确保您已成功登录教务系统,并选定正确的学期。",
+            "好的,开始"
+        );
+        if (confirmed) {
+            console.log("用户点击了确认按钮。Alert Promise Resolved: " + confirmed);
+            AndroidBridge.showToast("Alert:用户点击了确认!");
+            return true; // 成功时返回 true
+        } else {
+            console.log("用户点击了取消按钮或关闭了弹窗。Alert Promise Resolved: " + confirmed);
+            AndroidBridge.showToast("Alert:用户取消了!");
+            return false; // 用户取消时返回 false
+        }
+    } catch (error) {
+        console.error("显示公告弹窗时发生错误:", error);
+        AndroidBridge.showToast("Alert:显示弹窗出错!" + error.message);
+        return false; // 出现错误时也返回 false
+    }
+}
+
+// 2. 获取学期信息
+async function getSemesterIndex() {
+    try {
+        const response = await fetch(`https://bk.cup.edu.cn/student/for-std/course-table`)
+        const htmlString = await response.text();
+        const parser = new DOMParser();
+        const dom = parser.parseFromString(htmlString, 'text/html');
+        const selectElement = dom.getElementById('semesters') || dom.getElementById('allSemesters');
+        
+        if (!selectElement) {
+            throw new Error("页面中未找到学期选择框");
+        }
+
+        // 1. 将所有 option 转换为数组
+        const options = Array.from(selectElement.options);
+        
+        // 2. 过滤掉 "全部学期" (value="all"),因为导入课表通常只能导具体的某一学期
+        const validOptions = options.filter(opt => opt.value !== "all");
+        
+        if (validOptions.length === 0) {
+            throw new Error("未解析到有效的学期列表");
+        }
+
+        // 3. 提取用于展示的文本数组和用于请求的 value 数组
+        const semesterTexts = validOptions.map(opt => opt.text);  // 例: ["2025-2026-2", "2025-2026-1", ...]
+        const semesterValues = validOptions.map(opt => opt.value); // 例: ["191", "171", ...]
+
+        // 4. 调用安卓原生弹窗,让用户选择
+        const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
+            "选择学期", 
+            JSON.stringify(semesterTexts), // 必须是 JSON 字符串
+            0 // 默认选中第一个(通常是最新学期)
+        );
+
+        // 5. 判断用户的选择结果
+        if (selectedIndex !== null && selectedIndex >= 0) {
+            // 根据选中的索引,获取对应的学期 ID (value)
+            const selectedValue = semesterValues[selectedIndex];
+            if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
+                AndroidBridge.showToast("已选择学期: " + semesterTexts[selectedIndex]);
+            }
+            return selectedValue; // 成功时返回学期编号 (例如 "191")
+        } else {
+            // 用户取消了选择
+            console.log("用户取消了学期选择");
+            return null;
+        }
+    } catch (error) {
+        console.error("获取学期信息时发生错误:", error);
+        AndroidBridge.showToast("Alert:获取学期信息出错!" + error.message);
+        return null; // 出现错误时返回 null
+    }    
+}
+
+// 3. 获取课程数据
+async function fetchPrintData(semesterIndex) {
+    try {
+        const responds = await fetch(`https://bk.cup.edu.cn/student/for-std/course-table/semester/${semesterIndex}/print-data`);
+        if (!responds.ok) {
+            throw new Error(`网络请求失败,状态码: ${responds.status}`);
+        }
+        const printData = await responds.json();
+        return printData;
+    } catch (error) {
+        console.error("获取数据时发生错误:", error);
+        AndroidBridge.showToast("Alert:获取数据出错!" + error.message);
+        return null;
+    }
+}
+
+// 4. 导入课程数据
+async function parseCourses(printData) {
+    console.log("正在导入课程数据...");
+
+    const activities = printData.studentTableVms[0].activities;
+    const parsedCourses = activities.map(activity => {
+        // 返回拾光要求的标准结构
+        return {
+            name: activity.courseName,                                      // 课程名称
+            teacher: activity.teachers ? activity.teachers.join(" ") : "",  // 授课教师
+            position: activity.campus ? `${activity.campus} ${activity.room}` : (activity.room || "未知地点"),
+                                                                            // 上课地点
+            day: activity.weekday,                                          // 星期几 (1-7)
+            startSection: activity.startUnit,                               // 开始节次
+            endSection: activity.endUnit,                                   // 结束节次
+            weeks: activity.weekIndexes                                     // 上课周次数组
+        };
+    });
+
+    try {
+        console.log("正在尝试导入课程...");
+        const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
+        if (result === true) {
+            console.log("课程导入成功!");
+            AndroidBridge.showToast("测试课程导入成功!");
+        } else {
+            console.log("课程导入未成功,结果:" + result);
+            AndroidBridge.showToast("测试课程导入失败,请查看日志。");
+        }
+    } catch (error) {
+        console.error("导入课程时发生错误:", error);
+        AndroidBridge.showToast("导入课程失败: " + error.message);
+    }
+}
+
+// 5. 导入预设时间段
+async function importPresetTimeSlots(printData) {
+    console.log("正在准备预设时间段数据...");
+
+    function formatTime(timeInt) {
+        // 将数字转为字符串,并在前面补0直到长度为4
+        const timeStr = timeInt.toString().padStart(4, '0'); 
+        // 截取前两位作为小时,后两位作为分钟,中间加冒号
+        return `${timeStr.slice(0, 2)}:${timeStr.slice(2, 4)}`;
+    }
+
+    const courseUnitList = printData.studentTableVms[0].timeTableLayout.courseUnitList;
+    const presetTimeSlots = courseUnitList.map(unit => {
+        return {
+            number: unit.indexNo,                 // 节次编号
+            startTime: formatTime(unit.startTime), // 开始时间
+            endTime: formatTime(unit.endTime)      // 结束时间
+        };
+    });
+    
+    try {
+        console.log("正在尝试导入预设时间段...");
+        const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
+        if (result === true) {
+            console.log("预设时间段导入成功!");
+            window.AndroidBridge.showToast("测试时间段导入成功!");
+        } else {
+            console.log("预设时间段导入未成功,结果:" + result);
+            window.AndroidBridge.showToast("测试时间段导入失败,请查看日志。");
+        }
+        return result; // 返回导入结果,供流程控制使用
+    } catch (error) {
+        console.error("导入时间段时发生错误:", error);
+        window.AndroidBridge.showToast("导入时间段失败: " + error.message);
+    }
+}
+
+// 6. 导入课表配置
+async function saveConfig(semesterIndex) {
+    console.log("正在准备配置数据...");
+
+    const responds = await fetch(`https://bk.cup.edu.cn/student/ws/semester/get/${semesterIndex}`);
+    if (!responds.ok) {
+        throw new Error(`网络请求失败,状态码: ${responds.status}`);
+    }
+    const semesterInfo = await responds.json();
+    const startDate = new Date(semesterInfo.startDate);
+    const endDate = new Date(semesterInfo.endDate);
+    const diffDays = Math.ceil(Math.abs(endDate - startDate) / (1000 * 60 * 60 * 24));
+    const calculatedWeeks = Math.ceil(diffDays / 7);
+    // 注意:只传入要修改的字段,其他字段(如 semesterTotalWeeks)会使用 Kotlin 模型中的默认值
+    const courseConfigData = {
+        "semesterStartDate": semesterInfo.startDate,
+        "semesterTotalWeeks": calculatedWeeks,
+        "defaultClassDuration": 45,
+        "defaultBreakDuration": 5,
+        "firstDayOfWeek": semesterInfo.weekStartOnSunday ? 7 : 1
+    };
+
+    try {
+        console.log("正在尝试导入课表配置...");
+        const configJsonString = JSON.stringify(courseConfigData);
+
+        const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
+
+        if (result === true) {
+            console.log("课表配置导入成功!");
+            AndroidBridge.showToast("测试配置导入成功!开学日期: " + startDate.toISOString().split('T')[0]);
+        } else {
+            console.log("课表配置导入未成功,结果:" + result);
+            AndroidBridge.showToast("测试配置导入失败,请查看日志。");
+        }
+        return result; // 返回导入结果,供流程控制使用
+    } catch (error) {
+        console.error("导入配置时发生错误:", error);
+        AndroidBridge.showToast("导入配置失败: " + error.message);
+    }
+}
+
+
+/**
+ * 编排整个课程导入流程。
+ * 在任何一步用户取消或发生错误时,都会立即退出,AndroidBridge.notifyTaskCompletion()应该只在成功后调用  
+ */
+async function runImportFlow() {
+    AndroidBridge.showToast("课程导入流程即将开始...");
+
+    // 1. 公告和前置检查。
+    const alertConfirmed = await promptUserToStart();
+    if (!alertConfirmed) {
+        return; // 用户取消,立即退出函数
+    }
+    
+    // 2. 获取学期。
+    const semesterIndex = await getSemesterIndex();
+    if (semesterIndex === null) {
+        AndroidBridge.showToast("导入已取消。");
+        // 用户取消,直接退出
+        return;
+    }
+
+    // 3. 获取课程数据
+    const printData = await fetchPrintData(semesterIndex);
+    if (printData === null) {
+        AndroidBridge.showToast("导入已取消。");
+        // 请求失败或无数据,直接退出
+        return;
+    }
+
+    // 4. 解析课程信息。
+    const courses = await parseCourses(printData);
+    if (courses === null) {
+        // 请求失败或无数据,直接退出
+        return;
+    }
+
+    // 5. 导入时间段数据。
+    const timeSlotImportResult = await importPresetTimeSlots(printData);
+    if (!timeSlotImportResult) {
+        // 时间段导入失败,直接退出
+        return;
+    }
+    
+    // 6. 保存配置数据 (例如学期开始日期)
+    const configSaveResult = await saveConfig(semesterIndex);
+    if (!configSaveResult) {
+        // 保存配置失败,直接退出
+        return;
+    }
+
+    // 7. 流程**完全成功**,发送结束信号。
+    AndroidBridge.showToast("所有任务已完成!");
+    AndroidBridge.notifyTaskCompletion();
+}
+
+// 启动所有演示
+runImportFlow();