Pārlūkot izejas kodu

Merge branch 'pending' of https://github.com/XingHeYuZhuan/shiguang_warehouse into pending

XingHeYuZhuan 3 dienas atpakaļ
vecāks
revīzija
466bca09c7

+ 21 - 1
index/root_index.yaml

@@ -148,6 +148,11 @@ schools:
     initial: "X"
     resource_folder: "XMCU"
 
+  - id: "XZIT"
+    name: "徐州工程学院"
+    initial: "X"
+    resource_folder: "XZIT"
+
   - id: "HBLGXY"
     name: "淮北理工学院"
     initial: "H"
@@ -271,4 +276,19 @@ schools:
   - id: "HYNU"
     name: "衡阳师范学院"
     initial: "H"
-    resource_folder: "HYNU"    
+    resource_folder: "HYNU"    
+
+  - id: "JNU"    
+    name: "暨南大学" 
+    initial: "J"          
+    resource_folder: "JNU"   
+    
+  - id: "HQU"
+    name: "华侨大学"
+    initial: "H"
+    resource_folder: "HQU"
+
+  - id: "UJN"
+    name: "济南大学"
+    initial: "J"
+    resource_folder: "UJN"

+ 1 - 1
resources/CAPU/capadap.js

@@ -1,4 +1,4 @@
-// 文件: school.js
+// 文件: capadap.js
 
 /**
  * 显示导入提示

+ 1 - 1
resources/CUP/adapters.yaml

@@ -14,4 +14,4 @@ adapters:
     asset_js_path: "cup_02.js"
     import_url: "https://gmis.cup.edu.cn/gmis/student/default/index"
     maintainer: "larryyan"
-    description: "点击统一身份认证登录\n打开(培养管理=>学生课表查询)页面。\n包含克拉玛依校区(上课时间错误请反馈开发者)。"
+    description: "点击统一身份认证登录\n打开(培养管理=>学生课表查询)页面。\n包含克拉玛依校区"

+ 408 - 436
resources/CUP/cup_02.js

@@ -1,469 +1,441 @@
 // resources/CUP/cup_02.js
+// 中国石油大学(北京)研究生拾光课程表适配脚本
+// https://gmis.cup.edu.cn/gmis/student/default/index
+// 教务平台:南京南软
+// 适配开发者:larryyan
+
+// ==========================================
+// 0. 全局配置与验证函数
+// ==========================================
+
+const PRESET_TIME_CONFIG = {
+    campuses: {
+        MainCampus: {
+            startTimes: {
+                morning: "08:00",
+                afternoon: "13:30",
+                evening: "18:30"
+            },
+            sectionCounts: {
+                morning: 4,
+                afternoon: 4,
+                evening: 3
+            },
+            durations: {
+                classMinutes: 45,
+                shortBreakMinutes: 5,
+                longBreakMinutes: 30
+            }
+        },
+        Karamay: {
+            startTimes: {
+                morning: "09:30",
+                afternoon: "16:00",
+                evening: "20:30"
+            },
+            sectionCounts: {
+                morning: 5,
+                afternoon: 4,
+                evening: 3
+            },
+            durations: {
+                classMinutes: 45,
+                shortBreakMinutes: 5,
+                longBreakMinutes: 20
+            },
+        }
+    },
+    common: {
+        longBreakAfter: {
+            morning: 2,
+            afternoon: 2,
+            evening: 0  // 晚间课程无大课间
+        }
+    }
+};
+
+const CAMPUS_OPTIONS = [
+    { id: "MainCampus", label: "主校区" },
+    { id: "Karamay", label: "克拉玛依校区" }
+];
+
+/**
+ * 验证开学日期的输入格式
+ */
+function validateDateInput(input) {
+    if (/^\d{4}[-\/\.]\d{2}[-\/\.]\d{2}$/.test(input)) {
+        return false; 
+    } else {
+        return "请输入正确的日期格式,例如: 2025-09-01"; 
+    }
+}
+
+// ==========================================
+// 业务流程函数
+// ==========================================
 
 // 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
-    }
+    console.log("即将显示公告弹窗...");
+    const confirmed = await window.AndroidBridgePromise.showAlert(
+        "重要通知",
+        "导入前请确保您已成功登录教务系统,并选定正确的学期。",
+        "好的,开始"
+    );
+    return confirmed === true;
 }
 
-// 2. 选择校区
+// 2. 选择校区 (使用配置项)
 async function selectCampus() {
-    try {
-        const campuses = ["本校", "克拉玛依校区"];
-        
-        // 呼叫安卓原生弹窗
-        const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
-            "选择所在校区", 
-            JSON.stringify(campuses), 
-            0 // 默认选中第一个(本校)
-        );
-
-        if (selectedIndex !== null && selectedIndex >= 0) {
-            const selectedCampus = campuses[selectedIndex];
-            if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
-                AndroidBridge.showToast("已选择: " + selectedCampus);
-            }
-            // 返回 true 代表是克拉玛依校区,返回 false 代表是本校
-            return selectedIndex === 1; 
-        } else {
-            // 用户取消了选择
-            if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
-                AndroidBridge.showToast("取消导入:未选择校区。");
-            }
-            return null;
-        }
-    } catch (error) {
-        console.error("选择校区时发生错误:", error);
-        return null; 
-    }    
+    // 从配置中提取用于展示的名称数组
+    const campusLabels = CAMPUS_OPTIONS.map(opt => opt.label);
+    
+    const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
+        "选择所在校区", 
+        JSON.stringify(campusLabels), 
+        0 
+    );
+
+    if (selectedIndex !== null && selectedIndex >= 0) {
+        const selectedCampus = CAMPUS_OPTIONS[selectedIndex];
+        if (!selectedCampus) {
+            throw new Error("校区选择结果无效。");
+        }
+        // 返回选中的校区 ID ("MainCampus" 或 "Karamay")
+        return selectedCampus.id; 
+    }
+
+    return null;
 }
 
 // 3. 获取学期信息
 async function getTermCode() {
-    try {
-        if (typeof AndroidBridge !== 'undefined') AndroidBridge.showToast("正在获取学期列表...");
-
-        // 检查环境是否支持 jQuery
-        if (typeof $ === 'undefined' || !$.ajax) {
-            throw new Error("未检测到 jQuery 环境,请确保在正确的课表页面执行。");
-        }
-
-        // 1. 请求学期列表数据
-        const termData = await new Promise((resolve, reject) => {
-            $.ajax({
-                type: 'get',
-                dataType: 'json',
-                url: '/gmis/default/bindterm',
-                cache: false, // 自动附加时间戳防止缓存
-                success: function (data) {
-                    resolve(data);
-                },
-                error: function (xhr, status, error) {
-                    reject(new Error(`网络请求失败,状态码: ${xhr.status} ${error}`));
-                }
-            });
-        });
-
-        if (!termData || termData.length === 0) {
-            throw new Error("未能获取到有效的学期列表数据。");
-        }
-
-        // 2. 提取文本、值,并寻找当前默认学期的索引
-        const semesterTexts = [];
-        const semesterValues = [];
-        let defaultSelectedIndex = 0; // 默认选中第一项
-
-        termData.forEach((item, index) => {
-            semesterTexts.push(item.termname);
-            semesterValues.push(item.termcode);
-            // 如果数据中带有 selected: true,则将其设为默认选中
-            if (item.selected) {
-                defaultSelectedIndex = index;
-            }
-        });
-
-        // 3. 呼叫安卓原生弹窗
-        const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
-            "选择导入学期", 
-            JSON.stringify(semesterTexts), 
-            defaultSelectedIndex
-        );
-
-        // 4. 判断用户选择结果
-        if (selectedIndex !== null && selectedIndex >= 0) {
-            const selectedValue = semesterValues[selectedIndex];
-            if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
-                AndroidBridge.showToast("已选择学期: " + semesterTexts[selectedIndex]);
-            }
-            return selectedValue; 
-        } else {
-            // 用户取消了选择
-            if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
-                AndroidBridge.showToast("取消导入:未选择学期。");
-            }
-            return null;
-        }
-
-    } catch (error) {
-        console.error("读取学期信息时发生错误:", error);
-        if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
-            AndroidBridge.showToast("Alert:读取学期信息出错!" + error.message);
-        }
-        return null; 
-    }    
+    if (typeof $ === 'undefined' || !$.ajax) {
+        throw new Error("未检测到 jQuery 环境,请确保在正确的课表页面执行。");
+    }
+
+    const termData = await new Promise((resolve, reject) => {
+        $.ajax({
+            type: 'get',
+            dataType: 'json',
+            url: '/gmis/default/bindterm',
+            cache: false, 
+            success: function (data) { resolve(data); },
+            error: function (xhr, status, error) { reject(new Error(`网络请求失败,状态码: ${xhr.status} ${error}`)); }
+        });
+    });
+
+    if (!termData || termData.length === 0) {
+        throw new Error("未能获取到有效的学期列表数据。");
+    }
+
+    const semesterTexts = [];
+    const semesterValues = [];
+    let defaultSelectedIndex = 0; 
+
+    termData.forEach((item, index) => {
+        semesterTexts.push(item.termname);
+        semesterValues.push(item.termcode);
+        if (item.selected) {
+            defaultSelectedIndex = index;
+        }
+    });
+
+    const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
+        "选择导入学期", 
+        JSON.stringify(semesterTexts), 
+        defaultSelectedIndex
+    );
+
+    if (selectedIndex !== null && selectedIndex >= 0) {
+        return semesterValues[selectedIndex];
+    }
+
+    return null;
 }
 
 // 4. 获取课程数据
 async function fetchData(termCode) {
-    try {
-        // 检查环境是否支持 jQuery 拦截解密
-        if (typeof $ === 'undefined' || !$.ajax) {
-            throw new Error("未检测到 jQuery 环境,请确保在正确的课表页面执行。");
-        }
-
-        // 将 $.ajax 包装成标准的 Promise,无缝融入 async/await 流程
-        const response = await new Promise((resolve, reject) => {
-            $.ajax({
-                type: 'post',
-                dataType: 'json',
-                url: "../pygl/py_kbcx_ew",
-                data: { 'kblx': 'xs', 'termcode': termCode },
-                cache: false,
-                success: function (data) {
-                    resolve(data);
-                },
-                error: function (xhr, status, error) {
-                    reject(new Error(`网络请求失败,状态码: ${xhr.status} ${error}`));
-                }
-            });
-        });
-
-        // 校验返回的数据结构
-        if (!response || !response.rows) {
-            throw new Error("接口返回数据为空或解密后格式不正确");
-        }
-
-        return response.rows;
-
-    } catch (error) {
-        console.error("获取数据时发生错误:", error);
-        AndroidBridge.showToast("Alert:获取数据出错!" + error.message);
-        return null;
-    }
+    if (typeof $ === 'undefined' || !$.ajax) {
+        throw new Error("未检测到 jQuery 环境,请确保在正确的课表页面执行。");
+    }
+
+    const response = await new Promise((resolve, reject) => {
+        $.ajax({
+            type: 'post',
+            dataType: 'json',
+            url: "../pygl/py_kbcx_ew",
+            data: { 'kblx': 'xs', 'termcode': termCode },
+            cache: false,
+            success: function (data) { resolve(data); },
+            error: function (xhr, status, error) { reject(new Error(`网络请求失败,状态码: ${xhr.status} ${error}`)); }
+        });
+    });
+
+    if (!response || !response.rows) {
+        throw new Error("接口返回数据为空或解密后格式不正确");
+    }
+
+    return response.rows;
 }
 
 // 5. 导入课程数据
-async function parseCourses(py_kbcx_ew, isKaramayCampus) {
-    console.log("正在解析研究生课程数据...");
-    
-    // 用于存放每一小节课的临时数组
-    let allCourseBlocks = [];
-
-    // 辅助函数 1:将 jcid 转换为标准的拾光节次 (1~12节)
-    // 根据数据:上午11-15 -> 1-5节,下午21-24 -> 6-9节,晚上31-33 -> 10-12节
-    function getStandardSection(jcid) {
-        if (jcid >= 11 && jcid <= 15) return jcid - 10;
-        if (jcid >= 21 && jcid <= 24) return jcid - 20 + 5; 
-        if (jcid >= 31 && jcid <= 33) return jcid - 30 + 9;
-        return 1; // 默认兜底
-    }
-
-    // 辅助函数 2:解析类似 "连续周 1-12周" 或 "单周 1-11周" 的字符串,返回数字数组
-    function parseWeeks(weekStr) {
-        let weeks = [];
-        let isSingle = weekStr.includes('单');
-        let isDouble = weekStr.includes('双');
-
-        // 匹配字符串里的所有数字或数字范围 (如 "1", "1-12")
-        let matches = weekStr.match(/\d+-\d+|\d+/g);
-        if (matches) {
-            matches.forEach(m => {
-                if (m.includes('-')) {
-                    let [start, end] = m.split('-').map(Number);
-                    for (let i = start; i <= end; i++) {
-                        if (isSingle && i % 2 === 0) continue;
-                        if (isDouble && i % 2 !== 0) continue;
-                        weeks.push(i);
-                    }
-                } else {
-                    let w = Number(m);
-                    if (isSingle && w % 2 === 0) return;
-                    if (isDouble && w % 2 !== 0) return;
-                    weeks.push(w);
-                }
-            });
-        }
-        return [...new Set(weeks)].sort((a, b) => a - b);
-    }
-
-    // --- 第一步:将按“行”排列的数据,拆解提取出每一小节课 ---
-    py_kbcx_ew.forEach(row => {
-        if (!isKaramayCampus && row.jcid === 15) {
-            return; 
-        }
-
-        let currentSection = getStandardSection(row.jcid);
-        // 遍历星期一 (z1) 到星期日 (z7)
-        for (let day = 1; day <= 7; day++) {
-            let zVal = row['z' + day];
-            if (zVal) {
-                // 如果同一个时间有两门课(比如单双周不同),按 <br/> 拆分
-                let classParts = zVal.split(/<br\s*\/?>/i); 
-                
-                classParts.forEach(part => {
-                    // 核心正则表达式:匹配 "课程名[周次]老师[地点]"
-                    // 兼容没有老师或没有地点的情况
-                    let match = part.match(/(.*?)\[(.*?)\]([^\[]*)(?:\[(.*?)\])?$/);
-                    
-                    if (match) {
-                        allCourseBlocks.push({
-                            name: match[1].trim(),                   // 提取:课程名
-                            weekStr: match[2].trim(),                // 提取:原始周次字符串 (用于后续比对)
-                            weeks: parseWeeks(match[2]),             // 解析:纯数字周次数组
-                            teacher: match[3] ? match[3].trim() : "",// 提取:老师
-                            position: match[4] ? match[4].trim() : "未知地点", // 提取:上课地点
-                            day: day,                                // 星期几
-                            section: currentSection                  // 当前是第几节
-                        });
-                    }
-                });
-            }
-        }
-    });
-
-    // --- 第二步:将连续的小节课“合并”成一门完整的课 ---
-    let mergedCourses = [];
-    allCourseBlocks.forEach(block => {
-        // 寻找是否已经有相邻的课可以合并 (同星期、同课名、同老师、同地点、同周次,且节次刚好挨着)
-        let existingCourse = mergedCourses.find(c => 
-            c.day === block.day &&
-            c.name === block.name &&
-            c.teacher === block.teacher &&
-            c.position === block.position &&
-            c.weekStr === block.weekStr &&
-            c.endSection === block.section - 1 // 核心:判断是否紧挨着上一节
-        );
-
-        if (existingCourse) {
-            // 如果可以合并,就把结束节次往后延
-            existingCourse.endSection = block.section;
-        } else {
-            // 如果不能合并,就作为一门新课加入
-            mergedCourses.push({
-                name: block.name,
-                teacher: block.teacher,
-                position: block.position,
-                day: block.day,
-                startSection: block.section,
-                endSection: block.section,
-                weeks: block.weeks,
-                weekStr: block.weekStr // 保留用于比对合并
-            });
-        }
-    });
-
-    // 清理掉多余的辅助比对字段,输出最终给拾光 App 的标准格式
-    const finalCourses = mergedCourses.map(c => {
-        delete c.weekStr; 
-        return c;
-    });
-
-    console.log("最终生成的标准课表数据:", finalCourses);
-
-    try {
-        console.log("正在尝试导入课程...");
-        const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(finalCourses));
-        if (result === true) {
-            console.log("课程导入成功!");
-            AndroidBridge.showToast("测试课程导入成功!");
-        } else {
-            console.log("课程导入未成功,结果:" + result);
-            AndroidBridge.showToast("测试课程导入失败,请查看日志。");
-        }
-    } catch (error) {
-        console.error("导入课程时发生错误:", error);
-        AndroidBridge.showToast("导入课程失败: " + error.message);
-    }
+async function parseCourses(py_kbcx_ew, isKaramayCampus) {    
+    // 用于存放每一小节课的临时数组
+    let allCourseBlocks = [];
+
+    // 辅助函数 1:根据 jcid 转换成标准的节次编号
+    function getStandardSection(jcid) {
+        if (jcid >= 11 && jcid <= 15) return jcid - 10;
+        let afternoonOffset = isKaramayCampus ? 5 : 4;
+        if (jcid >= 21 && jcid <= 24) return jcid - 20 + afternoonOffset; 
+        let eveningOffset = isKaramayCampus ? 9 : 8;
+        if (jcid >= 31 && jcid <= 33) return jcid - 30 + eveningOffset;
+        return 1; // 默认兜底
+    }
+
+    // 辅助函数 2:解析类似 "连续周 1-12周" 或 "单周 1-11周" 的字符串,返回数字数组
+    function parseWeeks(weekStr) {
+        let weeks = [];
+        let isSingle = weekStr.includes('单');
+        let isDouble = weekStr.includes('双');
+
+        let matches = weekStr.match(/\d+-\d+|\d+/g);
+        if (matches) {
+            matches.forEach(m => {
+                if (m.includes('-')) {
+                    let [start, end] = m.split('-').map(Number);
+                    for (let i = start; i <= end; i++) {
+                        if (isSingle && i % 2 === 0) continue;
+                        if (isDouble && i % 2 !== 0) continue;
+                        weeks.push(i);
+                    }
+                } else {
+                    let w = Number(m);
+                    if (isSingle && w % 2 === 0) return;
+                    if (isDouble && w % 2 !== 0) return;
+                    weeks.push(w);
+                }
+            });
+        }
+        return [...new Set(weeks)].sort((a, b) => a - b);
+    }
+
+    // --- 第一步:将按“行”排列的数据,拆解提取出每一小节课 ---
+    py_kbcx_ew.forEach(row => {
+        // 本校区强行剔除上午第5节 (jcid === 15)
+        if (!isKaramayCampus && row.jcid === 15) {
+            return; 
+        }
+
+        let currentSection = getStandardSection(row.jcid);
+        // 遍历星期一 (z1) 到星期日 (z7)
+        for (let day = 1; day <= 7; day++) {
+            let zVal = row['z' + day];
+            if (zVal) {
+                // 如果同一个时间有两门课(比如单双周不同),按 <br/> 拆分
+                let classParts = zVal.split(/<br\s*\/?>/i); 
+                
+                classParts.forEach(part => {
+                    let match = part.match(/(.*?)\[(.*?)\]([^\[]*)(?:\[(.*?)\])?$/);
+                    
+                    if (match) {
+                        allCourseBlocks.push({
+                            name: match[1].trim(),                   // 提取:课程名
+                            weekStr: match[2].trim(),                // 提取:原始周次字符串 (用于后续比对)
+                            weeks: parseWeeks(match[2]),             // 解析:纯数字周次数组
+                            teacher: match[3] ? match[3].trim() : "",// 提取:老师
+                            position: match[4] ? match[4].trim() : "未知地点", // 提取:上课地点
+                            day: day,                                // 星期几
+                            section: currentSection                  // 当前是第几节
+                        });
+                    }
+                });
+            }
+        }
+    });
+
+    // --- 第二步:将连续的小节课“合并”成一门完整的课 ---
+    let mergedCourses = [];
+    allCourseBlocks.forEach(block => {
+        // 寻找是否已经有相邻的课可以合并 (同星期、同课名、同老师、同地点、同周次,且节次刚好挨着)
+        let existingCourse = mergedCourses.find(c => 
+            c.day === block.day &&
+            c.name === block.name &&
+            c.teacher === block.teacher &&
+            c.position === block.position &&
+            c.weekStr === block.weekStr &&
+            c.endSection === block.section - 1 // 核心:判断是否紧挨着上一节
+        );
+
+        if (existingCourse) {
+            // 如果可以合并,就把结束节次往后延
+            existingCourse.endSection = block.section;
+        } else {
+            // 如果不能合并,就作为一门新课加入
+            mergedCourses.push({
+                name: block.name,
+                teacher: block.teacher,
+                position: block.position,
+                day: block.day,
+                startSection: block.section,
+                endSection: block.section,
+                weeks: block.weeks,
+                weekStr: block.weekStr 
+            });
+        }
+    });
+
+    // 清理掉多余的辅助比对字段,输出最终给拾光 App 的标准格式
+    const finalCourses = mergedCourses.map(c => {
+        delete c.weekStr; 
+        return c;
+    });
+
+    const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(finalCourses));
+    if (result !== true) {
+        throw new Error("课程导入失败,请查看日志。");
+    }
 }
 
 // 6. 导入预设时间段
-async function importPresetTimeSlots(campusIsKaramay) {
-    console.log("正在准备预设时间段数据...");
-    const presetTimeSlots = [
-        { "number": 1, "startTime": "08:00", "endTime": "08:45" },
-        { "number": 2, "startTime": "08:50", "endTime": "09:35" },
-        { "number": 3, "startTime": "10:05", "endTime": "10:50" },
-        { "number": 4, "startTime": "10:55", "endTime": "11:40" },
-        { "number": 5, "startTime": "13:30", "endTime": "14:15" },
-        { "number": 6, "startTime": "14:20", "endTime": "15:05" },
-        { "number": 7, "startTime": "15:35", "endTime": "16:20" },
-        { "number": 8, "startTime": "16:25", "endTime": "17:10" },
-        { "number": 9, "startTime": "18:30", "endTime": "19:15" },
-        { "number": 10, "startTime": "19:20", "endTime": "20:05" },
-        { "number": 11, "startTime": "20:10", "endTime": "20:55" },
-        { "number": 12, "startTime": "21:00", "endTime": "21:45" }
-    ];
-
-    const presetTimeSlotsKaramay = [
-        { "number": 1, "startTime": "08:00", "endTime": "08:45" },
-        { "number": 2, "startTime": "08:50", "endTime": "09:35" },
-        { "number": 3, "startTime": "10:05", "endTime": "10:50" },
-        { "number": 4, "startTime": "10:55", "endTime": "11:40" },
-        { "number": 5, "startTime": "12:00", "endTime": "12:45" },
-        { "number": 6, "startTime": "13:30", "endTime": "14:15" },
-        { "number": 7, "startTime": "14:20", "endTime": "15:05" },
-        { "number": 8, "startTime": "15:35", "endTime": "16:20" },
-        { "number": 9, "startTime": "16:25", "endTime": "17:10" },
-        { "number": 10, "startTime": "18:30", "endTime": "19:15" },
-        { "number": 11, "startTime": "19:20", "endTime": "20:05" },
-        { "number": 12, "startTime": "20:10", "endTime": "20:55" },
-        { "number": 13, "startTime": "21:00", "endTime": "21:45" }
-    ];
-
-    try {
-        console.log("正在尝试导入预设时间段...");
-        const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(campusIsKaramay ? presetTimeSlotsKaramay : presetTimeSlots));
-        if (result === true) {
-            console.log("预设时间段导入成功!");
-            window.AndroidBridge.showToast("测试时间段导入成功!");
-        } else {
-            console.log("预设时间段导入未成功,结果:" + result);
-            window.AndroidBridge.showToast("测试时间段导入失败,请查看日志。");
-        }
-    } catch (error) {
-        console.error("导入时间段时发生错误:", error);
-        window.AndroidBridge.showToast("导入时间段失败: " + error.message);
-    }
+async function importPresetTimeSlots(campusId) {    
+    const campusConfig = PRESET_TIME_CONFIG.campuses[campusId];
+    const commonConfig = PRESET_TIME_CONFIG.common;
+    const generatedSlots = [];
+    let currentSectionNum = 1;
+
+    // 辅助函数:把 HH:mm 转换成分钟数 (例如 08:00 -> 480)
+    function timeToMinutes(timeStr) {
+        const [h, m] = timeStr.split(':').map(Number);
+        return h * 60 + m;
+    }
+
+    // 辅助函数:把分钟数转换成 HH:mm
+    function minutesToTime(mins) {
+        const h = Math.floor(mins / 60).toString().padStart(2, '0');
+        const m = (mins % 60).toString().padStart(2, '0');
+        return `${h}:${m}`;
+    }
+
+    // 按照上午、下午、晚上的顺序生成
+    const periods = ["morning", "afternoon", "evening"];
+    
+    periods.forEach(period => {
+        const count = campusConfig.sectionCounts[period];
+        if (count === 0) return; // 如果该时段没课,跳过
+
+        let currentMins = timeToMinutes(campusConfig.startTimes[period]);
+        const longBreakPos = commonConfig.longBreakAfter[period];
+
+        for (let i = 1; i <= count; i++) {
+            const startStr = minutesToTime(currentMins);
+            currentMins += campusConfig.durations.classMinutes; // 加上课时间
+            const endStr = minutesToTime(currentMins);
+
+            generatedSlots.push({
+                number: currentSectionNum,
+                startTime: startStr,
+                endTime: endStr
+            });
+
+            currentSectionNum++;
+
+            // 如果不是该时段的最后一节课,则加上课间休息时间,推算出下一节的开始时间
+            if (i < count) {
+                if (i === longBreakPos) {
+                    currentMins += campusConfig.durations.longBreakMinutes;
+                } else {
+                    currentMins += campusConfig.durations.shortBreakMinutes;
+                }
+            }
+        }
+    });
+
+    const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(generatedSlots));
+    if (result !== true) {
+        throw new Error("时间段导入失败,请查看日志。");
+    }
 }
 
-/**
- * 验证开学日期的输入格式
- * 规范:验证通过返回 false,验证失败返回 错误信息字符串
- */
-function validateDateInput(input) {
-    // 匹配 YYYY-MM-DD, YYYY/MM/DD, YYYY.MM.DD 格式
-    if (/^\d{4}[-\/\.]\d{2}[-\/\.]\d{2}$/.test(input)) {
-        return false; // 验证通过
-    } else {
-        return "请输入正确的日期格式,例如: 2025-09-01"; // 验证失败,原生 UI 会显示此提示
-    }
-}
 
 // 7. 导入课表配置
 async function saveConfig() {
-    console.log("正在准备配置数据...");
-
-    let startDate = await window.AndroidBridgePromise.showPrompt(
-        "输入开学日期", 
-        "请输入本学期开学日期 (格式: YYYY-MM-DD):",
-        "2025-09-01",          // 默认文本,给用户一个参考
-        "validateDateInput"    // 传入我们上面定义的全局验证函数名
-    );
-
-    // 如果返回 null,说明用户点击了取消
-    if (startDate === null) {
-        if (typeof AndroidBridge !== 'undefined') {
-            AndroidBridge.showToast("已取消开学日期设置,将使用默认配置。");
-        }
-        startDate = "2025-09-01"; // 兜底默认值,保证流程继续
-    } else {
-        // 容错处理:验证函数放过了 / 和 . ,我们在保存前把它统一替换成标准的横杠 -
-        startDate = startDate.trim().replace(/[\/\.]/g, '-');
-    }
-
-    // 注意:只传入要修改的字段,其他字段(如 semesterTotalWeeks)会使用 Kotlin 模型中的默认值
-    const courseConfigData = {
-        "semesterStartDate": startDate,
-        "semesterTotalWeeks": 25,
-        "defaultClassDuration": 45,
-        "defaultBreakDuration": 5,
-        "firstDayOfWeek": 1
-    };
-
-    try {
-        console.log("正在尝试导入课表配置...");
-        const configJsonString = JSON.stringify(courseConfigData);
-
-        const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
-
-        if (result === true) {
-            console.log("课表配置导入成功!");
-            AndroidBridge.showToast("测试配置导入成功!开学日期: " + startDate);
-        } else {
-            console.log("课表配置导入未成功,结果:" + result);
-            AndroidBridge.showToast("测试配置导入失败,请查看日志。");
-        }
-    } catch (error) {
-        console.error("导入配置时发生错误:", error);
-        AndroidBridge.showToast("导入配置失败: " + error.message);
-    }
+    let startDate = await window.AndroidBridgePromise.showPrompt(
+        "输入开学日期", 
+        "请输入本学期开学日期 (格式: YYYY-MM-DD):",
+        "2025-09-01",          
+        "validateDateInput"    
+    );
+
+    if (startDate === null) {
+        startDate = "2025-09-01"; 
+    } else {
+        startDate = startDate.trim().replace(/[\/\.]/g, '-');
+    }
+
+    const courseConfigData = {
+        "semesterStartDate": startDate,
+        "semesterTotalWeeks": 25,
+        "defaultClassDuration": 45,
+        "defaultBreakDuration": 5,
+        "firstDayOfWeek": 1
+    };
+
+    const configJsonString = JSON.stringify(courseConfigData);
+    const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
+    if (result !== true) {
+        throw new Error("导入配置失败,请查看日志。");
+    }
 }
 
-
 /**
- * 编排整个课程导入流程。
- * 在任何一步用户取消或发生错误时,都会立即退出,AndroidBridge.notifyTaskCompletion()应该只在成功后调用  
- */
+ * 编排整个课程导入流程。
+ */
 async function runImportFlow() {
-    AndroidBridge.showToast("课程导入流程即将开始...");
-
-    // 1. 公告和前置检查。
-    const alertConfirmed = await promptUserToStart();
-    if (!alertConfirmed) {
-        return; // 用户取消,立即退出函数
-    }
-    
-    // 2. 选择校区。
-    const isKaramayCampus = await selectCampus();
-    if (isKaramayCampus === null) return;
-
-    // 3. 获取学期。
-    const termCode = await getTermCode();
-    if (termCode === null) {
-        AndroidBridge.showToast("导入已取消。");
-        // 用户取消,直接退出
-        return;
-    }
-
-    // 4. 获取课程数据
-    const py_kbcx_ew = await fetchData (termCode);
-    if (py_kbcx_ew === null) {
-        AndroidBridge.showToast("导入已取消。");
-        // 请求失败或无数据,直接退出
-        return;
-    }
-
-    // 5. 解析课程信息。
-    await parseCourses(py_kbcx_ew, isKaramayCampus);
-    
-    // 6. 导入时间段数据。
-    await importPresetTimeSlots(isKaramayCampus);
-    
-    // 7. 保存配置数据 (例如学期开始日期)
-    await saveConfig();
-
-    // 8. 流程**完全成功**,发送结束信号。
-    AndroidBridge.showToast("所有任务已完成!");
-    AndroidBridge.notifyTaskCompletion();
+    try {
+        // 1. 公告和前置检查。
+        const alertConfirmed = await promptUserToStart();
+        if (!alertConfirmed) {
+            throw new Error("导入已取消。");
+        }
+        
+        // 2. 选择校区。 (获取校区ID)
+        const campusId = await selectCampus();
+        if (campusId === null) {
+            throw new Error("导入已取消:未选择校区。");
+        }
+        
+        // 生成一个 boolean 给解析课程使用
+        const isKaramayCampus = (campusId === "Karamay");
+
+        // 3. 获取学期。
+        const termCode = await getTermCode();
+        if (termCode === null) {
+            throw new Error("导入已取消:未选择学期。");
+        }
+
+        // 4. 获取课程数据
+        const py_kbcx_ew = await fetchData(termCode);
+
+        // 5. 解析课程信息。 (传入 boolean)
+        await parseCourses(py_kbcx_ew, isKaramayCampus);
+        
+        // 6. 导入时间段数据。 (传入字符串 campusId,供引擎推算)
+        await importPresetTimeSlots(campusId);
+        
+        // 7. 保存配置数据 
+        await saveConfig();
+
+        // 8. 流程**完全成功**,发送结束信号。
+        AndroidBridge.notifyTaskCompletion();
+    } catch (error) {
+        const message = error && error.message ? error.message : "导入流程执行失败。";
+        if (typeof AndroidBridge !== 'undefined') {
+            AndroidBridge.showToast(message);
+        }
+        console.error("runImportFlow error:", error);
+    }
 }
 
 // 启动所有演示

+ 9 - 0
resources/HQU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/CUST/adapters.yaml
+adapters:
+  - adapter_id: "HQU"
+    adapter_name: "华侨大学教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "hquadap.js"
+    import_url: "https://atrust.hqu.edu.cn/portal/?redirectid=0.6668972084789097#/login"
+    maintainer: "Xuan-Xuann"
+    description: "适配华侨大学。登录成功后选择个人课表查询,进入后等待加载完毕即可导入。"

+ 130 - 0
resources/HQU/hquadap.js

@@ -0,0 +1,130 @@
+/**
+ * 华侨大学 (HQU) 教务系统课程导入脚本
+ * 版本:2026 春季学期
+ * 还有待解决的问题:课程合并 
+ */
+
+// 1. 全局验证函数 (由 showPrompt 调用)
+function validateTermInput(input) {
+    if (/^\d{4}-\d{4}-\d$/.test(input)) {
+        return false; // 校验通过
+    } else {
+        return "格式错误!请输入如 2025-2026-2";
+    }
+}
+
+async function runImportFlow() {
+    AndroidBridge.showToast("正在启动华大教务同步程序...");
+
+    try {
+        // --- 1. 获取学期代码 ---
+        const termQuery = [
+            { name: "CSDM", linkOpt: "AND", builderList: "cbl_String", builder: "equal", value: "PK" },
+            { name: "ZCSDM", linkOpt: "AND", builderList: "cbl_String", builder: "equal", value: "XSDXNXQDM" }
+        ];
+        
+        const termResp = await fetch("https://jwapp-hqu-edu-cn-s.atrust.hqu.edu.cn:9443/jwapp/sys/wdkb/modules/xskcb/xtcscx.do?sf_request_type=ajax", {
+            method: "POST",
+            headers: { "content-type": "application/x-www-form-urlencoded" },
+            body: `querySetting=${encodeURIComponent(JSON.stringify(termQuery))}`
+        });
+        const termJson = await termResp.json();
+        const currentXNXQ = termJson.datas.xtcscx.CSZA || "2025-2026-2";
+
+        const parts = currentXNXQ.split('-');
+        const currentXN = `${parts}-${parts}`;
+        const currentXQ = parts;
+
+        // --- 2. 获取配置与开学日期 ---
+        let startDate = "2026-03-02";
+        let totalWeeks = 18;
+        try {
+            const configResp = await fetch("https://jwapp-hqu-edu-cn-s.atrust.hqu.edu.cn:9443/jwapp/sys/wdkb/modules/jshkcb/cxjcs.do?sf_request_type=ajax", {
+                method: "POST",
+                headers: { "content-type": "application/x-www-form-urlencoded" },
+                body: `XN=${currentXN}&XQ=${currentXQ}`
+            });
+            const configJson = await configResp.json();
+            const schoolConfig = configJson.datas.cxjcs.rows;
+            if (schoolConfig && schoolConfig.XQKSRQ) {
+                startDate = schoolConfig.XQKSRQ.split(' ');
+                totalWeeks = parseInt(schoolConfig.ZZC) || 18;
+            }
+        } catch (e) { console.log("配置抓取跳过"); }
+
+        // --- 3. 抓取课表详情 ---
+        const kbResp = await fetch("https://jwapp-hqu-edu-cn-s.atrust.hqu.edu.cn:9443/jwapp/sys/wdkb/modules/xskcb/cxxszhxqkb.do?sf_request_type=ajax", {
+            method: "POST",
+            headers: { "content-type": "application/x-www-form-urlencoded" },
+            body: `XNXQDM=${currentXNXQ}&XNXQDM2=${currentXNXQ}&XNXQDM3=${currentXNXQ}`
+        });
+        const kbJson = await kbResp.json();
+        const rawRows = kbJson.datas.cxxszhxqkb.rows;
+
+        //转换数据结构
+        const parsedCourses = rawRows.map(item => {
+    // 1. 周次解析:从位图获取最硬核的数据
+        const weeks = [];
+        const bitMap = item.SKZC || "";
+            
+
+    // 华大逻辑:如果位图索引1是1,那就是第一周有课  你看它里面  "SKZC": "011111111111111100",
+    for (let i = 0; i < bitMap.length; i++) {
+        if (bitMap[i] === '1') {
+            weeks.push(i + 1); 
+        }
+    }
+            
+    // 体育课的标题:体育课加上具体的项目(如:篮球)
+    let courseName = item.KCM;
+    if (item.TYXMDM_DISPLAY) {
+        courseName = `${courseName}(${item.TYXMDM_DISPLAY})`;
+    }
+
+    // 地点兜底:优先取具体教室,没有则取教学楼,再没有则标记操场
+    const position = item.JASMC || item.JXLDM_DISPLAY || "操场/待定";
+
+    // 2. YPSJDD 
+    let note = `原始安排:${item.YPSJDD || '无'}`;
+    if (item.XF) note += `\n学分:${item.XF}`;
+    if (item.KCXZDM_DISPLAY) note += `\n性质:${item.KCXZDM_DISPLAY}`;
+
+    return {
+        name: item.KCM,
+        teacher: item.SKJS || item.JSM || "未知",
+        position: position,
+        day: parseInt(item.SKXQ),
+        startSection: parseInt(item.KSJC),
+        endSection: parseInt(item.JSJC),
+        weeks: weeks,
+    };
+});
+
+        // --- 5. 提交数据 ---
+        await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
+            semesterStartDate: startDate,
+            semesterTotalWeeks: totalWeeks
+        }));
+
+        const timeSlots = [
+            { number: 1, startTime: "08:00", endTime: "08:45" }, { number: 2, startTime: "08:55", endTime: "09:40" },
+            { number: 3, startTime: "10:00", endTime: "10:45" }, { number: 4, startTime: "10:55", endTime: "11:40" },
+            { 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:20", endTime: "19:05" },
+            { number: 11, startTime: "19:10", endTime: "19:55" }, { number: 12, startTime: "20:05", endTime: "20:50" },
+            { number: 13, startTime: "20:55", endTime: "21:40" }
+        ];
+        await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+
+        await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
+
+        AndroidBridge.showToast(`${currentXNXQ} 导入成功!`);
+        AndroidBridge.notifyTaskCompletion();
+
+    } catch (e) {
+        await window.AndroidBridgePromise.showAlert("导入失败", "错误: " + e.message, "重试");
+    }
+}
+
+runImportFlow();

+ 9 - 0
resources/JNU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/JNU/adapters.yaml
+adapters:
+  - adapter_id: "JNU_01"
+    adapter_name: "暨南大学金智教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "jnu_01.js"
+    import_url: "https://jw.jnu.edu.cn"
+    maintainer: "White-Dew-Boy"
+    description: "登录教务系统后进入我的课表界面。自动导入上课时间段仅适用于珠海校区,其它校区可自行配置"

+ 467 - 0
resources/JNU/jnu_01.js

@@ -0,0 +1,467 @@
+//拾光课程表适配JNU脚本(fetch API)
+//本脚本的时间段自动导入仅适配珠海校区,其它校区可自行重新设置上课时间段
+
+//生成年份数组
+const yearArr = [];
+for(let i=1990; i<=2100; i++){
+    yearArr.push(i);
+}
+
+//获取本月最大日期
+function getDaysInMonth(year, month) {
+    return new Date(year, month, 0).getDate();
+}
+
+//根据URL特征判断当前所在界面
+function checkCurrentPage() {
+    const currenturl = window.location.href.toLowerCase(); //获取当前URL并转为小写
+    if (currenturl.includes("login?")) {
+        return 1; //未登录,返回值1
+    }
+    if (currenturl.includes("new/index.html")) {
+        return 2; //在新教务系统首页,没有进入课表,返回值2
+    }
+    if (currenturl.includes("jwapp/sys/wdkb")) {
+        return 0; //成功登录且进入我的课表界面,返回值0
+    }
+    return -1; //未知错误,返回值-1
+}
+
+//显示一个公告信息弹窗
+async function confirmAlert() {
+    try {
+        const confirmed = await window.AndroidBridgePromise.showAlert(
+            "重要提醒",
+            "自动加载上课时间段仅支持珠海校区,\n每周第一天默认为星期日,可自行修改。\n是否确认获取课表?",
+            "确认",
+        );
+        if (confirmed) {
+            AndroidBridge.showToast("正在尝试导入课表...");
+            return true; // 成功时返回 true
+        } else {
+            AndroidBridge.showToast("已取消!");
+            return false; // 用户取消时返回 false
+        }
+    } catch (error) {
+        AndroidBridge.showToast("显示弹窗出错!" + error.message);
+        return false; // 出现错误时也返回 false
+    }
+}
+
+//显示一个是否手动输入学年学期信息的弹窗
+async function confirmGetXNXQAlert() {
+    try {
+        const confirmed = await window.AndroidBridgePromise.showAlert(
+            "重要提醒",
+            "是否确认手动输入学年学期信息?",
+            "确认",
+        );
+        if (confirmed) {
+            return true; // 成功时返回 true
+        } else {
+            AndroidBridge.showToast("已取消!");
+            return false; // 用户取消时返回 false
+        }
+    } catch (error) {
+        AndroidBridge.showToast("显示弹窗出错!" + error.message);
+        return false; // 出现错误时也返回 false
+    }
+}
+
+//向用户获取学年信息
+async function getSession() {
+    try {
+        const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
+            "请选择学年的起始年(如2025-2026学年的2025)",
+            JSON.stringify(yearArr),
+            36
+        );
+        if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < yearArr.length) {
+            AndroidBridge.showToast("你选择了" + yearArr[selectedIndex] + '-' + (Number(yearArr[selectedIndex])+1) + "学年");
+            return (yearArr[selectedIndex] + '-' + (Number(yearArr[selectedIndex])+1));
+        } else {
+            AndroidBridge.showToast("用户取消了选择!");
+            return -1; // 用户取消时返回-1
+        }
+    } catch (error) {
+        AndroidBridge.showToast("显示列表出错!" + error.message);
+        return -1; // 出现错误时也返回-1
+    }
+}
+
+//向用户获取学期信息
+async function getSemester() {
+    try {
+        const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
+            "请选择学期(1-第一学期,2-第二学期)",
+            JSON.stringify([1,2]),
+            1
+        );
+        if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < 2) {
+            if(selectedIndex === 0) {
+                AndroidBridge.showToast("你选择了第一学期");
+                return 1;
+            }else{
+                AndroidBridge.showToast("你选择了第二学期");
+                return 2;
+            }
+        } else {
+            AndroidBridge.showToast("用户取消了选择!");
+            return -1; // 用户取消时返回 -1
+        }
+    } catch (error) {
+        AndroidBridge.showToast("显示列表出错!" + error.message);
+        return -1; // 出现错误时也返回 -1
+    }
+}
+
+//导入预设时间段
+async function importPresetTimeSlots() {
+    //预设节次信息,仅适用于珠海校区
+    const timeSlots = [
+        {"number":1,"startTime":"08:00","endTime":"08:45"},
+        {"number":2,"startTime":"08:55","endTime":"09:40"},
+        {"number":3,"startTime":"10:00","endTime":"10:45"},
+        {"number":4,"startTime":"10:55","endTime":"11:40"},
+        {"number":5,"startTime":"12:40","endTime":"13:25"},
+        {"number":6,"startTime":"13:35","endTime":"14:20"},
+        {"number":7,"startTime":"14:30","endTime":"15:15"},
+        {"number":8,"startTime":"15:25","endTime":"16:10"},
+        {"number":9,"startTime":"16:20","endTime":"17:05"},
+        {"number":10,"startTime":"17:15","endTime":"18:00"},
+        {"number":11,"startTime":"19:00","endTime":"19:45"},
+        {"number":12,"startTime":"19:55","endTime":"20:40"},
+        {"number":13,"startTime":"20:50","endTime":"21:35"}
+    ]
+
+    try {
+        const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+        if (result === true) {
+            //AndroidBridge.showToast("时间段导入成功!");
+        } else {
+            AndroidBridge.showToast("时间段导入失败!");
+        }
+    } catch (error) {
+        AndroidBridge.showToast("导入时间段失败: " + error.message);
+    }
+}
+
+
+//使用DOM方法自动获取当前课表的学年学期信息
+function getSessionSemester() {
+    const oriData = document.getElementById('dqxnxq2');
+    if (!oriData) {
+        return -1;
+    }
+    const xnxqID = oriData.getAttribute('value');
+    if (!xnxqID) {
+        return -1;
+    }
+    return xnxqID;
+}
+
+//使用fetch API 获取课程表信息
+async function getCourses(xnxqID) {
+    const fetchConfig = {
+        url:'https://jw.jnu.edu.cn/jwapp/sys/wdkb/modules/xskcb/xskcb.do',
+        method:'POST',
+        params:{
+            XNXQDM:xnxqID
+        }
+    }
+
+    try {
+        const response = await fetch(fetchConfig.url,
+            {method:fetchConfig.method,
+                headers:{'content-type':'application/x-www-form-urlencoded; charset=UTF-8'},
+                credentials:'include',
+                body:new URLSearchParams(fetchConfig.params)});
+        if (!response.ok) {
+            throw new Error(`请求失败:${response.status} ${response.statusText}`);
+        }
+        const courseData = await response.json();
+        if (courseData.datas.xskcb.totalSize === 0){
+            AndroidBridge.showToast("获取到的课表为空课表,请检查学年学期信息!")
+            return -1;
+        }
+        AndroidBridge.showToast("成功获取课程表信息,共" + courseData.datas.xskcb.totalSize + "门课程");
+        return courseData;
+    }catch (error){
+        AndroidBridge.showToast("获取课程表失败:" + error.message);
+        return -1;
+    }
+}
+
+//处理原始数据中的周数,输出对应数组
+function handleWeekNum(oriData){
+    const result = [];
+    if(oriData.includes('(')){
+        const weekStr = oriData.slice(0,-4);//去掉最后四位
+        const startWeek = weekStr.split('-')[0];
+        const endWeek = weekStr.split('-')[1];
+        if(oriData.includes('单')){
+            for(let i = Number(startWeek); i<=Number(endWeek); i++){
+                if(i%2 !== 0){
+                    result.push(i);
+                }
+            }
+        }else{
+            for(let i = Number(startWeek); i<=Number(endWeek); i++){
+                if(i%2 === 0){
+                    result.push(i);
+                }
+            }
+        }
+    }else{
+        const weekStr = oriData.slice(0,-1); //去掉最后一位
+        const startWeek = weekStr.split('-')[0];
+        const endWeek = weekStr.split('-')[1];
+        for(let i = Number(startWeek); i<=Number(endWeek); i++){
+            result.push(i);
+        }
+    }
+    return result;
+}
+
+//处理原始数据中的节次信息
+function handleSectionNum(oriData) {
+    const result = [];
+    const coursesNumStr = oriData.slice(1,-1); //去掉最后一位和第一位
+    const startSection = coursesNumStr.split('-')[0];
+    const endSection = coursesNumStr.split('-')[1];
+    result.push(Number(startSection)); //第一位为开始节次
+    result.push(Number(endSection)); //第二位为结束节次
+    return result;
+}
+
+//处理原始数据中的星期信息
+function handleDayNum(OriData) {
+    const weekMap = [
+        "星期一",
+        "星期二",
+        "星期三",
+        "星期四",
+        "星期五",
+        "星期六",
+        "星期日",
+    ]
+    for(let i=0; i<7; i++){
+        if(OriData === weekMap[i])
+            return i+1;
+    }
+    return null;
+}
+
+// 解析得到的JSON数据
+function analysisCoursesInfo(oriCourseData) {
+    const courses = [];
+    const totalCourseNum = oriCourseData.datas.xskcb.totalSize;
+    if(totalCourseNum === 0){
+        return courses;
+    }
+    const coursesRows = oriCourseData.datas.xskcb.rows || [];
+    for(let i=0; i<totalCourseNum; i++){
+        const courseName = coursesRows[i].KCM ?? "未知课程";
+        const teacher = coursesRows[i].SKJS ?? "未知教师";
+        const position = coursesRows[i].JASMC ?? "未知地点";
+        const courseTime = coursesRows[i].SKSJ.split(' '); //第一为周次,第二位为天次,第三位为节次
+        const day = handleDayNum(courseTime[1]); //得到星期几上课
+        const sectionInfo = {
+            startSection:handleSectionNum(courseTime[2])[0], //开始节次
+            endSection:handleSectionNum(courseTime[2])[1] //结束节次
+        }
+        const weeks = handleWeekNum(courseTime[0]);
+        const courseInfo = {
+            "name": courseName,        // 课程名称 (String)
+            "teacher": teacher,      // 教师姓名 (String)
+            "position": position,      // 上课地点 (String)
+            "day": day,                 // 星期几 (Int, 1=周一, 7=周日)
+            "startSection": sectionInfo.startSection,        // 开始节次 (Int, 如果 isCustomTime 为 false 或未提供,则必填)
+            "endSection": sectionInfo.endSection,          // 结束节次 (Int, 如果 isCustomTime 为 false 或未提供,则必填)
+            "weeks": weeks,    // 上课周数 (Int Array, 必须是数字数组,例如 [1, 3, 5, 7])
+        }
+        courses.push(courseInfo);
+    }
+    return courses;
+}
+
+//自动获取总星期数
+function autoGetTotalWeekNum(courses) {
+    let totalWeekNum = 0;
+    let currentNum = 0;
+    for(let i=0; i<courses.length; i++){
+        currentNum = courses[i].weeks[courses[i].weeks.length-1];
+        if(currentNum > totalWeekNum) {
+            totalWeekNum = currentNum;
+        }
+    }
+    return totalWeekNum;
+}
+
+//导入课程数据,同时返回得到的总星期数
+async function saveCourses(courseData) {
+    let xnxqID = getSessionSemester();
+    if (xnxqID === -1) {
+        AndroidBridge.showToast("无法自动获取学年学期信息!");
+        if(await confirmGetXNXQAlert()){
+            const session = await getSession();
+            if(session === -1){
+                return -1;
+            }
+            const semester = await getSemester();
+            if(semester === -1){
+                return -1;
+            }
+            xnxqID = (session + '-' + semester);
+        }else {
+            return -1;
+        }
+    }else{
+        AndroidBridge.showToast("自动获取到学年学期信息:" + xnxqID);
+    }
+
+    const rawResponse = await getCourses(xnxqID);
+    if(rawResponse === -1) {
+        return -1;
+    }
+
+    const courses = analysisCoursesInfo(rawResponse);
+    const totalWeeksNum = autoGetTotalWeekNum(courses);
+
+    try {
+        const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (result === true) {
+            //AndroidBridge.showToast("课程导入成功!");
+            return totalWeeksNum;
+        } else {
+            AndroidBridge.showToast("课程导入失败!");
+            return -1
+        }
+    } catch (error) {
+        AndroidBridge.showToast("导入课程失败: " + error.message);
+        return -1;
+    }
+
+}
+
+//导入课表配置
+async function saveConfig(semesterTotalWeeks) {
+    // 注意:只传入要修改的字段,其他字段(如 semesterTotalWeeks)会使用 Kotlin 模型中的默认值
+    let semesterStartData = "";
+    let choseResult = 1;
+
+    //显示三次单项选择框,向用户获取开学时间
+    try {
+        const yearIndex = await window.AndroidBridgePromise.showSingleSelection(
+            "请选择开学时间(年份)",
+            JSON.stringify(yearArr),
+            36
+        );
+        if (yearIndex !== null && yearIndex >= 0 && yearIndex < yearArr.length) {
+            const year = yearArr[yearIndex];
+            const monthIndex = await window.AndroidBridgePromise.showSingleSelection(
+                "请选择开学时间(月份)",
+                JSON.stringify([1,2,3,4,5,6,7,8,9,10,11,12]),
+                2
+            );
+            if (monthIndex !== null && monthIndex >= 0 && monthIndex < 12) {
+                const month =[1,2,3,4,5,6,7,8,9,10,11,12][monthIndex];
+                const dayArr = [];
+                for (let i = 1; i <= getDaysInMonth(year,month); i++) {
+                    dayArr.push(i);
+                }
+                const dayIndex = await window.AndroidBridgePromise.showSingleSelection(
+                    "请选择开学时间(日期)",
+                    JSON.stringify(dayArr),
+                    7
+                );
+                if (dayIndex !== null && dayIndex >= 0 && dayIndex < dayArr.length) {
+                    const day = dayArr[dayIndex];
+                    const yearStr = year.toString();
+                    const monthStr = month.toString().padStart(2,"0"); //补充为两位
+                    const dayStr = day.toString().padStart(2,"0"); //补充为两位
+                    semesterStartData =  yearStr + '-' + monthStr + '-' + dayStr;
+                    AndroidBridge.showToast("你选择的开学时间为:" + semesterStartData);
+                } else {
+                    choseResult = 0;
+                }
+            } else {
+                choseResult = 0;
+            }
+        } else {
+            choseResult = 0;
+        }
+    } catch (error) {
+        AndroidBridge.showToast("显示列表出错!" + error.message);
+        return -1; // 出现错误时也返回 -1
+    }
+
+    if(!choseResult){
+        AndroidBridge.showToast("用户取消了选择,将按默认开学日期2026-3-9导入配置");
+        semesterStartData = "2026-03-09";
+    }
+
+
+
+    try {
+        const courseConfigData = {
+            "semesterStartDate": semesterStartData, //月份要使用两位数,否则软件会崩溃
+            "semesterTotalWeeks": Number(semesterTotalWeeks),
+            "defaultClassDuration": 45,
+            "defaultBreakDuration": 10,
+            "firstDayOfWeek": 7
+        };
+        const configJsonString = JSON.stringify(courseConfigData);
+        const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
+        if (result === true) {
+            return 0;
+            //AndroidBridge.showToast("课表配置导入成功!");
+        } else {
+            AndroidBridge.showToast("课表配置导入失败");
+            return -1;
+        }
+    } catch (error) {
+        AndroidBridge.showToast("导入配置失败: " + error.message);
+        return -1;
+    }
+}
+
+/**
+ * 编排这些异步操作,并在用户取消时停止后续执行。
+ */
+async function runAllDemosSequentially() {
+    //检查是否进入课表界面
+    const currentPageNum = checkCurrentPage();
+    if(currentPageNum === 1){
+        AndroidBridge.showToast("请登录教务系统");
+        return;
+    }else if(currentPageNum === 2) {
+        AndroidBridge.showToast("请进入我的课表界面");
+        return;
+    }else if(currentPageNum === -1) {
+        AndroidBridge.showToast("未知错误");
+        return;
+    }
+
+    //确认窗口
+    const alertResult = await confirmAlert();
+    if (!alertResult) {
+        return; // 用户取消,立即退出函数
+    }
+
+    const totalNum = await saveCourses();
+    if(totalNum === -1) {
+        return;
+    }
+    await importPresetTimeSlots();
+    const saveConfigResult = await saveConfig(totalNum);
+    if(saveConfigResult === -1)
+    {
+        return;
+    }
+
+    // 发送最终的生命周期完成信号
+    AndroidBridge.notifyTaskCompletion();
+}
+
+runAllDemosSequentially();

+ 8 - 0
resources/UJN/adapters.yaml

@@ -0,0 +1,8 @@
+adapters:
+  - adapter_id: "UJN_01"
+    adapter_name: "济南大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "school.js"
+    import_url: "https://sso.ujn.edu.cn/tpass/login?service=http%3A%2F%2Fjwgl.ujn.edu.cn%2Fsso%2Fdriotlogin"
+    maintainer: "Moyu"
+    description: "济南大学教务系统适配,支持班级课表和个人课表的导入,通过统一认证登录"

+ 282 - 0
resources/UJN/school.js

@@ -0,0 +1,282 @@
+/**
+ * 济南大学教务适配
+ * @since 2026-3-13
+ * @description 支持班级课表和个人课表的导入
+ * @author Moyu
+ * @version 1.0
+ */
+class CourseModel {
+  name = ""; // 课程名称 (String)
+  teacher = ""; // 教师姓名 (String)
+  position = ""; // 上课地点 (String)
+  day = 0; //星期几 (Int, 1=周一, 7=周日)
+  startSection = 0; // 开始节次 (Int, 如果 isCustomTime 为 false 或未提供,则必填)
+  endSection = 0; // 结束节次 (Int, 如果 isCustomTime 为 false 或未提供,则必填)
+  weeks = [0]; // 上课周数 (Int Array, 必须是数字数组,例如 [1, 3, 5, 7])
+  isCustomTime = false; // 是否使用自定义时间 (Boolean, 可选,默认为 false。如果为 true,则 customStartTime 和 customEndTime 必填;如果为 false 或未提供,则 startSection 和 endSection 必填)
+  customStartTime = ""; // 自定义开始时间 (String, 格式 HH:mm, 如果 isCustomTime 为 true 则必填)
+  customEndTime = ""; // 自定义结束时间 (String, 格式 HH:mm, 如果 isCustomTime 为 true 则必填)
+  constructor(
+    name, // 课程名称 (String)
+    teacher, // 教师姓名 (String)
+    position, // 上课地点 (String)
+    day, // 星期几 (Int, 1=周一,7=周日)
+    startSection, // 开始节次 (Int)
+    endSection, // 结束节次 (Int)
+    weeks = [], // 上课周数 (Int Array)
+    isCustomTime = false, // 是否自定义时间 (Boolean,默认false)
+    customStartTime = "", // 自定义开始时间 (可选)
+    customEndTime = "", // 自定义结束时间 (可选)
+  ) {
+    // 1. 基础字段赋值(必选参数)
+    this.name = name;
+    this.teacher = teacher;
+    this.position = position;
+    this.day = day;
+    this.startSection = startSection;
+    this.endSection = endSection;
+    this.weeks = weeks;
+    this.isCustomTime = isCustomTime;
+    this.customStartTime = customStartTime;
+    this.customEndTime = customEndTime;
+  }
+}
+class CustomTimeModel {
+  number = 0;
+  startTime = ""; // 开始时间 (String, 格式 HH:mm)
+  endTime = ""; // 结束时间 (String, 格式 HH:mm)
+  constructor(num, start, end) {
+    this.number = num;
+    this.startTime = start;
+    this.endTime = end;
+  }
+}
+
+const urlPersonnalClassTable =
+  "jwgl.ujn.edu.cn/jwglxt/kbcx/xskbcx_cxXskbcxIndex.html";
+const urlClassTable = "jwgl.ujn.edu.cn/jwglxt/kbdy/bjkbdy_cxBjkbdyIndex.html";
+
+//解析周数据
+function parseWeekText(text) {
+  if (!text) return [];
+  text = text.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "").trim();
+
+  const allWeeks = new Set();
+  const noJie = text.replace(/\(\d+-\d+节\)/g, " ");
+
+  const rangePattern = /(\d+)-(\d+)周(?:\((单|双)\))?/g;
+  const singlePattern = /(\d+)周(?:\((单|双)\))?/g;
+
+  let match;
+  while ((match = rangePattern.exec(noJie)) !== null) {
+    const [, start, end, type] = match;
+    for (let w = parseInt(start, 10); w <= parseInt(end, 10); w++) {
+      if (
+        !type ||
+        (type === "单" && w % 2 === 1) ||
+        (type === "双" && w % 2 === 0)
+      ) {
+        allWeeks.add(w);
+      }
+    }
+  }
+
+  const processedRanges = [];
+  rangePattern.lastIndex = 0;
+  while ((match = rangePattern.exec(noJie)) !== null) {
+    processedRanges.push({
+      start: match.index,
+      end: match.index + match[0].length,
+    });
+  }
+
+  singlePattern.lastIndex = 0;
+  while ((match = singlePattern.exec(noJie)) !== null) {
+    const weekNum = parseInt(match[1], 10);
+    const type = match[2];
+
+    // 检查这个匹配是否已经被范围正则匹配过了(简单判断:如果包含"-"就跳过)
+    const matchStr = match[0];
+    if (matchStr.includes("-")) continue;
+
+    if (
+      !type ||
+      (type === "单" && weekNum % 2 === 1) ||
+      (type === "双" && weekNum % 2 === 0)
+    ) {
+      allWeeks.add(weekNum);
+    }
+  }
+
+  return [...allWeeks].sort((a, b) => a - b);
+}
+function offsetColByRow(row) {
+  row = row - 2;
+  if (row % 4 == 0) {
+    return 0;
+  }
+  if (row % 4 == 2) {
+    return 1;
+  }
+}
+function analyzeCourseModel(item, flag) {
+  let td = item.closest("td");
+  let elements = item.querySelectorAll("p");
+  if (!td) {
+    console.error("找不到单元格");
+    return null;
+  }
+  let tr = td.parentElement;
+  let site = {
+    row: tr.rowIndex, //第几行
+    rowSpan: td.rowSpan || 1, //跨几行
+    col: td.cellIndex, //第几列
+    colSpan: td.colSpan || 1, //跨几列
+    cell: td, //本身
+  };
+  let currentItem = item.querySelector(".title");
+  let name = currentItem.textContent;
+  let teacher;
+  let position;
+  let weeks;
+  if (flag == 1) {
+    teacher = elements[2].lastElementChild.innerText;
+    position = elements[1].lastElementChild.innerText;
+    weeks = parseWeekText(elements[0].lastElementChild.innerText);
+  } else {
+    if (elements.length != 1) {
+      teacher =
+        elements[4].firstElementChild.nextSibling.textContent.split("(")[0];
+      position = elements[3].firstElementChild.nextSibling.textContent;
+      weeks = parseWeekText(
+        elements[2].firstElementChild.nextSibling.textContent,
+      );
+    } else {
+      teacher = "";
+      position = "";
+      weeks = parseWeekText("1-20周");
+    }
+  }
+  return new CourseModel(
+    name.replace(/[■☆★◆]/g, ""),
+    teacher.trim(),
+    position.trim(),
+    site.col - 1 + offsetColByRow(site.row),
+    site.row - 1,
+    site.row + site.rowSpan - 2,
+    [...weeks],
+  );
+}
+
+async function saveCourses() {
+  let flag = null;
+  let elements = [];
+  if (window.location.href.includes(urlPersonnalClassTable)) {
+    elements = document.querySelectorAll(
+      "#innerContainer #table1 div.timetable_con",
+    );
+    flag = 1;
+  } else {
+    if (window.location.href.includes(urlClassTable)) {
+      elements = document.querySelectorAll(
+        "#table1.tab-pane>.timetable1 div.timetable_con",
+      );
+      flag = 0;
+    }
+  }
+  let courseModels = [];
+  elements.forEach((item) => {
+    let course = analyzeCourseModel(item, flag);
+    if (course) {
+      courseModels.push({ ...course });
+    }
+  });
+
+  try {
+    await window.AndroidBridgePromise.saveImportedCourses(
+      JSON.stringify(courseModels),
+    );
+    return courseModels.length;
+  } catch (error) {
+    console.error("保存课程失败:", error);
+    window.AndroidBridge.showToast("保存课程失败,请重试");
+    return 0;
+  }
+}
+async function checkEnvirenment() {
+  const nowSite = window.location.href;
+
+  const tableType = ["班级课表", "个人课表"];
+  if (
+    !nowSite.includes(urlPersonnalClassTable) &&
+    !nowSite.includes(urlClassTable)
+  ) {
+    window.AndroidBridge.showToast("当前页面不在支持的导入范围内");
+    const selectedOption =
+      await window.AndroidBridgePromise.showSingleSelection(
+        "现在不在可导入的页面中,请选择导入班级课表还是个人课表,之后并确保打开具体课程页面",
+        JSON.stringify(tableType), // 必须是 JSON 字符串
+        -1, // 默认不选中
+      );
+    if (selectedOption === 0) {
+      clickMenu(
+        "N214505",
+        "/kbdy/bjkbdy_cxBjkbdyIndex.html",
+        "班级课表查询",
+        "null",
+      );
+      return false;
+    } else if (selectedOption === 1) {
+      clickMenu(
+        "N253508",
+        "/kbcx/xskbcx_cxXskbcxIndex.html",
+        "个人课表",
+        "null",
+      );
+      return false;
+    } else {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+async function runImportFlow() {
+  window.AndroidBridge.showToast("课程导入流程即将开始...");
+
+  if (!(await checkEnvirenment())) return;
+
+  const savedCourseCount = await saveCourses();
+  if (!savedCourseCount) {
+    return;
+  }
+  const slots = [
+    new CustomTimeModel(1, "08:00", "08:50"),
+    new CustomTimeModel(2, "08:55", "09:45"),
+    new CustomTimeModel(3, "10:15", "11:05"),
+    new CustomTimeModel(4, "11:10", "12:00"),
+    new CustomTimeModel(5, "14:00", "14:50"),
+    new CustomTimeModel(6, "14:55", "15:45"),
+    new CustomTimeModel(7, "16:15", "17:05"),
+    new CustomTimeModel(8, "17:10", "18:00"),
+    new CustomTimeModel(9, "19:00", "19:50"),
+    new CustomTimeModel(10, "19:55", "20:45"),
+    new CustomTimeModel(11, "20:50", "21:45"),
+  ];
+  try {
+    await window.AndroidBridgePromise.savePresetTimeSlots(
+      JSON.stringify(slots),
+    );
+  } catch (error) {
+    console.error("保存时间段失败:", error);
+    window.AndroidBridge.showToast("保存时间段失败,请重试");
+    return;
+  }
+  // 8. 流程**完全成功**,发送结束信号。
+  AndroidBridge.showToast(`导入成功:共 ${savedCourseCount} 门课程`);
+  AndroidBridge.notifyTaskCompletion();
+}
+
+// 启动导入流程
+runImportFlow();

+ 9 - 0
resources/XZIT/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/XZIT/adapters.yaml
+adapters:
+  - adapter_id: "XZIT_01"
+    adapter_name: "徐州工程学院教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "xzit_01.js"
+    import_url: "http://jwglxt.xzit.edu.cn/jwglxt/xtgl/login_slogin.html"
+    maintainer: "盐泡"
+    description: "徐州工程学院正方教务系统适配,登录选择个人课表页面导入即可。"

+ 279 - 0
resources/XZIT/xzit_01.js

@@ -0,0 +1,279 @@
+// 徐州工程学院(xzit.edu.cn) 拾光课程表适配脚本
+// 基于正方教务 HTML 页面抓取,出现问题请去学校校园墙寻找管理员 "盐泡"
+
+const TIME_SLOTS = [
+    { number: 1, startTime: "08:00", endTime: "08:45" },
+    { number: 2, startTime: "08:55", endTime: "09:40" },
+    { number: 3, startTime: "10:05", endTime: "10:50" },
+    { number: 4, startTime: "11:00", endTime: "11:45" },
+    { number: 5, startTime: "12:00", endTime: "12:45" },
+    { number: 6, startTime: "12:55", endTime: "13:40" },
+    { number: 7, startTime: "14:00", endTime: "14:45" },
+    { number: 8, startTime: "14:55", endTime: "15:40" },
+    { number: 9, startTime: "16:05", endTime: "16:50" },
+    { number: 10, startTime: "17:00", endTime: "17:45" },
+    { number: 11, startTime: "17:55", endTime: "18:40" },
+    { number: 12, startTime: "18:45", endTime: "19:30" },
+    { number: 13, startTime: "19:40", endTime: "20:25" },
+    { number: 14, startTime: "20:35", endTime: "21:20" }
+];
+
+function parseTable() {
+    const regexName = /[●★○]/g;
+    const courseInfoList = [];
+    const $ = window.jQuery;
+    if (!$) return courseInfoList;
+
+    $("#kbgrid_table_0 td").each((i, td) => {
+        if ($(td).hasClass("td_wrap") && $(td).text().trim() !== "") {
+            const day = parseInt($(td).attr("id").split("-")[0], 10);
+
+            $(td).find(".timetable_con.text-left").each((index, course) => {
+                const name = $(course).find(".title font").text().replace(regexName, "").trim();
+                const infoStr = $(course).find("p").eq(0).find("font").eq(1).text().trim();
+                const position = $(course).find("p").eq(1).find("font").text().trim();
+                const teacher = $(course).find("p").eq(2).find("font").text().trim();
+
+                if (infoStr && infoStr.match(/\((\d+-\d+节)\)/) && infoStr.split("节)")[1]) {
+                    const [sections, weeks] = parseCourseInfo(infoStr);
+                    if (name && position && teacher && sections.length && weeks.length) {
+                        courseInfoList.push({
+                            name: name,
+                            day: day,
+                            weeks: weeks,
+                            teacher: teacher,
+                            position: position.split(/\s+/).pop(),
+                            startSection: sections[0],
+                            endSection: sections[sections.length - 1]
+                        });
+                    }
+                }
+            });
+        }
+    });
+
+    return courseInfoList;
+}
+
+function parseList() {
+    const regexName = /[●★○]/g;
+    const regexWeekNum = /周数:|周/g;
+    const regexPosition = /上课地点:/g;
+    const regexTeacher = /教师 :/g;
+    const $ = window.jQuery;
+    if (!$) return [];
+
+    const courseInfoList = [];
+    $("#kblist_table tbody").each((day, tbody) => {
+        if (day > 0 && day < 8) {
+            let sections;
+            $(tbody).find("tr:not(:first-child)").each((trIndex, tr) => {
+                let name;
+                let font;
+
+                if ($(tr).find("td").length > 1) {
+                    sections = parseSections($(tr).find("td:first-child").text());
+                    name = $(tr).find("td:nth-child(2)").find(".title").text().replace(regexName, "").trim();
+                    font = $(tr).find("td:nth-child(2)").find("p font");
+                } else {
+                    name = $(tr).find("td").find(".title").text().replace(regexName, "").trim();
+                    font = $(tr).find("td").find("p font");
+                }
+
+                const weekStr = $(font[0]).text().replace(regexWeekNum, "").trim();
+                const weeks = parseWeeks(weekStr);
+                const positionRaw = $(font[1]).text().replace(regexPosition, "").trim();
+                const teacher = $(font[2]).text().replace(regexTeacher, "").trim();
+
+                if (name && sections && weeks.length && teacher && positionRaw) {
+                    courseInfoList.push({
+                        name: name,
+                        day: day,
+                        weeks: weeks,
+                        teacher: teacher,
+                        position: positionRaw.split(/\s+/).pop(),
+                        startSection: sections[0],
+                        endSection: sections[sections.length - 1]
+                    });
+                }
+            });
+        }
+    });
+
+    return courseInfoList;
+}
+
+function parseCourseInfo(str) {
+    const sections = parseSections(str.match(/\((\d+-\d+节)\)/)[1].replace(/节/g, ""));
+    const weekStrWithMarker = str.split("节)")[1];
+    const weeks = parseWeeks(weekStrWithMarker.replace(/周/g, "").trim());
+    return [sections, weeks];
+}
+
+function parseSections(str) {
+    const [start, end] = str.split("-").map(Number);
+    if (isNaN(start) || isNaN(end) || start > end) return [];
+    return Array.from({ length: end - start + 1 }, (_, i) => start + i);
+}
+
+function parseWeeks(str) {
+    const segments = str.split(",");
+    const weeks = [];
+    const segmentRegex = /(\d+)(?:-(\d+))?\s*(\([单双]\))?/g;
+
+    for (const segment of segments) {
+        const cleanSegment = segment.replace(/周/g, "").trim();
+        segmentRegex.lastIndex = 0;
+
+        let match;
+        while ((match = segmentRegex.exec(cleanSegment)) !== null) {
+            const start = parseInt(match[1], 10);
+            const end = match[2] ? parseInt(match[2], 10) : start;
+            const flagStr = match[3] || "";
+
+            let flag = 0;
+            if (flagStr.includes("单")) {
+                flag = 1;
+            } else if (flagStr.includes("双")) {
+                flag = 2;
+            }
+
+            for (let i = start; i <= end; i += 1) {
+                if (flag === 1 && i % 2 !== 1) continue;
+                if (flag === 2 && i % 2 !== 0) continue;
+                if (!weeks.includes(i)) {
+                    weeks.push(i);
+                }
+            }
+        }
+    }
+
+    return weeks.sort((a, b) => a - b);
+}
+
+function buildCourseConfig(courses) {
+    let maxWeek = 0;
+    for (const course of courses) {
+        for (const week of course.weeks) {
+            if (week > maxWeek) {
+                maxWeek = week;
+            }
+        }
+    }
+
+    return {
+        semesterTotalWeeks: maxWeek || 20,
+        firstDayOfWeek: 1
+    };
+}
+
+async function scrapeAndParseCourses() {
+    AndroidBridge.showToast("正在检查页面并抓取课程数据...");
+    const tips = "1. 登录徐州工程学院教务系统\n2. 进入学生个人课表页面\n3. 选择正确学年、学期并点击【查询】\n4. 确认页面已显示课表\n5. 点击下方【一键导入】";
+
+    try {
+        const response = await fetch(window.location.href);
+        const text = await response.text();
+        if (!text.includes("课表查询")) {
+            await window.AndroidBridgePromise.showAlert("导入失败", `当前页面似乎不是学生课表查询页面。请检查:\n${tips}`, "确定");
+            return null;
+        }
+
+        const typeElement = document.querySelector("#shcPDF");
+        if (!typeElement) {
+            await window.AndroidBridgePromise.showAlert("导入失败", "未能识别课表视图类型,请确认您已点击查询且课表已加载完毕。", "确定");
+            return null;
+        }
+
+        const type = typeElement.dataset.type;
+        const tableElement = document.querySelector(type === "list" ? "#kblist_table" : "#kbgrid_table_0");
+        if (!tableElement) {
+            await window.AndroidBridgePromise.showAlert("导入失败", `未能找到课表主体(${type} 视图),请确认您已点击查询且课表已加载完毕。`, "确定");
+            return null;
+        }
+
+        const courses = type === "list" ? parseList() : parseTable();
+        if (!courses.length) {
+            AndroidBridge.showToast("未找到任何课程数据,请检查学年学期是否正确或本学期无课。");
+            return null;
+        }
+
+        return {
+            courses: courses,
+            config: buildCourseConfig(courses)
+        };
+    } catch (error) {
+        AndroidBridge.showToast(`抓取或解析失败: ${error.message}`);
+        console.error("JS: Scrape/Parse Error:", error);
+        await window.AndroidBridgePromise.showAlert("抓取或解析失败", `发生错误:${error.message}`, "确定");
+        return null;
+    }
+}
+
+async function saveCourses(parsedCourses) {
+    AndroidBridge.showToast(`正在保存 ${parsedCourses.length} 门课程...`);
+    try {
+        await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses, null, 2));
+        return true;
+    } catch (error) {
+        AndroidBridge.showToast(`课程保存失败: ${error.message}`);
+        console.error("JS: Save Courses Error:", error);
+        return false;
+    }
+}
+
+async function saveCourseConfig(config) {
+    try {
+        await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
+    } catch (error) {
+        AndroidBridge.showToast(`课表配置保存失败: ${error.message}`);
+        console.error("JS: Save Config Error:", error);
+    }
+}
+
+async function importPresetTimeSlots() {
+    try {
+        await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(TIME_SLOTS));
+        AndroidBridge.showToast("预设时间段导入成功!");
+    } catch (error) {
+        AndroidBridge.showToast(`导入时间段失败: ${error.message}`);
+        console.error("JS: Save Time Slots Error:", error);
+    }
+}
+
+async function runImportFlow() {
+    const alertConfirmed = await window.AndroidBridgePromise.showAlert(
+        "徐州工程学院课表导入",
+        "导入前请确保您已在浏览器中成功登录教务系统,并处于课表查询页面且已点击查询。",
+        "好的,开始导入"
+    );
+    if (!alertConfirmed) {
+        AndroidBridge.showToast("用户取消了导入。");
+        return;
+    }
+
+    if (typeof window.jQuery === "undefined" && typeof $ === "undefined") {
+        const errorMsg = "当前教务系统页面似乎没有加载 jQuery 库。本脚本依赖 jQuery 进行 DOM 解析。";
+        AndroidBridge.showToast(errorMsg);
+        await window.AndroidBridgePromise.showAlert("导入失败", `${errorMsg}\n请尝试刷新页面后重试。`, "确定");
+        return;
+    }
+
+    const result = await scrapeAndParseCourses();
+    if (result === null) {
+        return;
+    }
+
+    const { courses, config } = result;
+    const saveResult = await saveCourses(courses);
+    if (!saveResult) {
+        return;
+    }
+
+    await saveCourseConfig(config);
+    await importPresetTimeSlots();
+    AndroidBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
+    AndroidBridge.notifyTaskCompletion();
+}
+
+runImportFlow();