Procházet zdrojové kódy

Merge pull request #464 from XingHeYuZhuan/pending

星河欲转 před 4 týdny
rodič
revize
999a08a9d4
3 změnil soubory, kde provedl 447 přidání a 1 odebrání
  1. 6 1
      index/root_index.yaml
  2. 9 0
      resources/GPNU/adapters.yaml
  3. 432 0
      resources/GPNU/gpnu_01.js

+ 6 - 1
index/root_index.yaml

@@ -721,4 +721,9 @@ schools:
   - id: "JSTC"
     name: "江苏旅游职业学院"
     initial: "J"
-    resource_folder: "JSTC"           
+    resource_folder: "JSTC"         
+
+  - id: "GPNU"
+    name: "广东技术师范大学"
+    initial: "G"
+    resource_folder: "GPNU"

+ 9 - 0
resources/GPNU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/BUPT/adapters.yaml
+adapters:
+  - adapter_id: "GPNU_01"
+    adapter_name: "广师大教务管理系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "gpnu_01.js"
+    import_url: "https://jwglxt.gpnu.edu.cn/sso/lyiotlogin"
+    maintainer: "moondish"
+    description: "适配教务管理系统,登录后导入个人课表(可不打开相应页面)如有bug请提交issues,泥雞又赢了"

+ 432 - 0
resources/GPNU/gpnu_01.js

@@ -0,0 +1,432 @@
+// ===================== 工具函数 =====================
+
+/**
+ * 解析周次字符串,例如 "1-16周"、"1-8周,10-16周"、"1-16周(单)" 等。
+ * @param {string} weekStr - 周次描述字符串
+ * @returns {number[]} 周次数字数组(升序)
+ */
+function parseWeeks(weekStr) {
+    if (!weekStr || typeof weekStr !== 'string') return [];
+
+    let cleanStr = weekStr.replace(/周/g, '').replace(/\s+/g, '');
+    const weeks = new Set();
+    const parts = cleanStr.split(',');
+
+    for (let rawPart of parts) {
+        if (!rawPart) continue; // 跳过空段,防止产生 0 周
+
+        let part = rawPart;
+        let oddOnly = false;
+        let evenOnly = false;
+
+        // 逐段检测单双周标记
+        if (/单/.test(part)) oddOnly = true;
+        if (/双/.test(part)) evenOnly = true;
+        part = part.replace(/\(单\)|\(双\)|单|双/g, '');
+
+        if (part.includes('-')) {
+            const [startStr, endStr] = part.split('-');
+            const start = Number(startStr);
+            const end = Number(endStr);
+            if (!isNaN(start) && !isNaN(end) && start <= end) {
+                for (let w = start; w <= end; w++) {
+                    if (oddOnly && w % 2 !== 1) continue;
+                    if (evenOnly && w % 2 !== 0) continue;
+                    weeks.add(w);
+                }
+            }
+        } else {
+            const w = Number(part);
+            if (!isNaN(w)) {
+                if (oddOnly && w % 2 !== 1) continue;
+                if (evenOnly && w % 2 !== 0) continue;
+                weeks.add(w);
+            }
+        }
+    }
+
+    return Array.from(weeks).sort((a, b) => a - b);
+}
+
+/**
+ * 解析 API 返回的 JSON 数据。
+ * @param {Object} jsonData - 教务系统返回的 JSON 对象
+ * @returns {Array} 解析后的课程数组
+ */
+function parseJsonData(jsonData) {
+    console.log("JS: parseJsonData 正在解析 JSON 数据...");
+
+    if (!jsonData || !Array.isArray(jsonData.kbList)) {
+        console.warn("JS: JSON 数据结构错误或缺少 kbList 字段。");
+        return [];
+    }
+
+    const rawCourseList = jsonData.kbList;
+    const finalCourseList = [];
+
+    for (const rawCourse of rawCourseList) {
+        if (!rawCourse.kcmc || !rawCourse.xm || !rawCourse.cdmc ||
+            !rawCourse.xqj || !rawCourse.jcs || !rawCourse.zcd) {
+            continue;
+        }
+
+        const weeksArray = parseWeeks(rawCourse.zcd);
+        if (weeksArray.length === 0) {
+            continue;
+        }
+
+        // 去除“节”字后再拆分,防止 "1-2节" 导致 NaN
+        const jcs = String(rawCourse.jcs).replace(/节/g, '');
+        const sectionParts = jcs.split('-');
+        const startSection = Number(sectionParts[0]);
+        const endSection = Number(sectionParts[sectionParts.length - 1]);
+
+        const day = Number(rawCourse.xqj);
+
+        if (isNaN(day) || isNaN(startSection) || isNaN(endSection) ||
+            day < 1 || day > 7 || startSection > endSection) {
+            continue;
+        }
+
+        finalCourseList.push({
+            name: rawCourse.kcmc.trim(),
+            teacher: rawCourse.xm.trim(),
+            position: rawCourse.cdmc.trim(),
+            day: day,
+            startSection: startSection,
+            endSection: endSection,
+            weeks: weeksArray
+        });
+    }
+
+    finalCourseList.sort((a, b) =>
+        a.day - b.day ||
+        a.startSection - b.startSection ||
+        a.name.localeCompare(b.name)
+    );
+
+    console.log(`JS: JSON 数据解析完成,共找到 ${finalCourseList.length} 门课程。`);
+    return finalCourseList;
+}
+
+// ===================== 全局验证函数 =====================
+
+function validateYearInput(input) {
+    console.log("JS: validateYearInput 被调用,输入: " + input);
+    if (input === null || input === undefined) {
+        return "请输入四位数字的学年喵~";
+    }
+    if (/^[0-9]{4}$/.test(input)) {
+        return false; // 校验通过
+    } else {
+        return "请输入四位数字的学年喵~";
+    }
+}
+
+function validateDateInput(input) {
+    if (input === null || input === undefined || input.trim() === '') {
+        return false; // 允许空值(留空跳过)
+    }
+    if (/^\d{4}-\d{2}-\d{2}$/.test(input.trim())) {
+        return false; // 校验通过
+    }
+    return "日期格式应为 YYYY-MM-DD,例如 2026-08-30";
+}
+
+// ===================== 与原生交互的异步封装 =====================
+
+async function promptUserToStart() {
+    console.log("JS: 流程开始:显示公告。");
+    return await window.shiguangBridgePromise.showAlert(
+        "教务系统课表导入",
+        "Ciallo~ 导入前请确保您已在浏览器中成功登录教务系统哦~",
+        "好的,开始导入"
+    );
+}
+
+async function getAcademicYear() {
+    const currentYear = new Date().getFullYear().toString();
+    console.log("JS: 提示用户输入学年。");
+    return await window.shiguangBridgePromise.showPrompt(
+        "选择学年喵~",
+        "请输入要导入课程的起始学年喵~(例如 2025-2026 应输入2025):",
+        currentYear,
+        "validateYearInput"
+    );
+}
+
+async function selectSemester() {
+    const semesters = ["第一学期", "第二学期"];
+    console.log("JS: 提示用户选择学期。");
+    const rawIndex = await window.shiguangBridgePromise.showSingleSelection(
+        "选择学期喵~",
+        JSON.stringify(semesters),
+        0
+    );
+    // 统一转换为数字,避免字符串索引导致判断错误
+    return Number(rawIndex);
+}
+
+async function selectArea() {
+    const areas = ["东/西/北校区", "白云校区", "河源校区"];
+    console.log("JS: 提示用户选择校区。");
+    const rawIndex = await window.shiguangBridgePromise.showSingleSelection(
+        "选择校区喵~",
+        JSON.stringify(areas),
+        0
+    );
+    // 统一转换为数字
+    return Number(rawIndex);
+}
+
+async function getSemesterStartDate() {
+    console.log("JS: 提示用户输入学期开始日期(可留空跳过)。");
+    const input = await window.shiguangBridgePromise.showPrompt(
+        "学期开始日期(可选)",
+        "请输入学期第一天的日期喵~(YYYY-MM-DD),留空则自动留空:",
+        "2026-09-07",
+        "validateDateInput"
+    );
+    if (input === null) {
+        console.log("JS: 用户取消了开始日期输入,继续流程。");
+        return null;
+    }
+    if (input.trim() === '') {
+        return null;
+    }
+    return input.trim();
+}
+
+function getSemesterCode(semesterIndex) {
+    // 3 表示第一学期,12 表示第二学期
+    return semesterIndex === 0 ? "3" : "12";
+}
+
+// ===================== 网络请求与课程解析 =====================
+
+async function fetchAndParseCourses(academicYear, semesterIndex) {
+    const semesterCode = getSemesterCode(semesterIndex);
+    const requestBody = `gnmkdm=N2151&xnm=${academicYear}&xqm=${semesterCode}&kzlx=ck&xsdm=&kclbdm=&kclxdm=`;
+
+    const targetUrls = [
+        "https://jwglxt.gpnu.edu.cn/jwglxt/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151"
+    ];
+
+    for (const url of targetUrls) {
+        try {
+            const response = await fetch(url, {
+                method: "POST",
+                headers: {
+                    "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
+                    "X-Requested-With": "XMLHttpRequest"
+                    // 注意:浏览器禁止手动设置 Origin 和 Referer,已移除。
+                },
+                body: requestBody,
+                credentials: "include"
+            });
+
+            if (response.ok) {
+                const jsonText = await response.text();
+                const jsonData = JSON.parse(jsonText);
+                if (jsonData && jsonData.kbList) {
+                    const parsedCourses = parseJsonData(jsonData);
+                    if (parsedCourses.length > 0) {
+                        const totalWeeks = inferTotalWeeks(parsedCourses);
+                        return {
+                            courses: parsedCourses,
+                            config: {
+                                semesterStartDate: null,
+                                semesterTotalWeeks: totalWeeks
+                            }
+                        };
+                    }
+                }
+            }
+        } catch (e) {
+            console.error(`Entry failed: ${url}`, e);
+        }
+    }
+
+    window.shiguangBridge.showToast("未能获取课表数据,请检查网络环境或登录状态。");
+    return null;
+}
+
+function inferTotalWeeks(courses) {
+    let maxWeek = 0;
+    for (const course of courses) {
+        const weekNums = course.weeks;
+        if (weekNums.length > 0) {
+            maxWeek = Math.max(maxWeek, ...weekNums);
+        }
+    }
+    return maxWeek || 20;
+}
+
+// ===================== 数据保存 =====================
+
+async function saveCourses(parsedCourses) {
+    window.shiguangBridge.showToast(`正在保存 ${parsedCourses.length} 门课程...`);
+    console.log(`JS: 尝试保存 ${parsedCourses.length} 门课程...`);
+    try {
+        await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
+        console.log("JS: 课程保存成功!");
+        return true;
+    } catch (error) {
+        window.shiguangBridge.showToast(`课程保存失败: ${error.message}`);
+        console.error('JS: Save Courses Error:', error);
+        return false;
+    }
+}
+
+// ===================== 三个校区的时间表 =====================
+
+// 东校区-西校区-北校区(统一)
+const TimeSlots_one = [
+    { number: 1, startTime: "08:20", endTime: "09:00" },
+    { number: 2, startTime: "09:10", endTime: "09:50" },
+    { number: 3, startTime: "10:00", endTime: "10:40" },
+    { number: 4, startTime: "10:50", endTime: "11:30" },
+    { number: 5, startTime: "13:30", endTime: "14:10" },
+    { number: 6, startTime: "14:20", endTime: "15:00" },
+    { number: 7, startTime: "15:10", endTime: "15:50" },
+    { number: 8, startTime: "16:00", endTime: "16:40" },
+    { number: 9, startTime: "18:40", endTime: "19:20" },
+    { number: 10, startTime: "19:30", endTime: "20:10" },
+    { number: 11, startTime: "20:20", endTime: "21:00" }
+];
+
+// 白云校区
+const TimeSlots_two = [
+    { number: 1, startTime: "08:30", endTime: "09:10" },
+    { number: 2, startTime: "09:15", endTime: "09:55" },
+    { number: 3, startTime: "10:05", endTime: "10:45" },
+    { number: 4, startTime: "10:50", endTime: "11:30" },
+    { number: 5, startTime: "13:30", endTime: "14:10" },
+    { number: 6, startTime: "14:15", endTime: "14:55" },
+    { number: 7, startTime: "15:05", endTime: "15:45" },
+    { number: 8, startTime: "15:50", endTime: "16:30" },
+    { number: 9, startTime: "18:40", endTime: "19:20" },
+    { number: 10, startTime: "19:25", endTime: "20:05" },
+    { number: 11, startTime: "20:10", endTime: "20:50" }
+];
+
+// 河源校区
+const TimeSlots_three = [
+    { number: 1, startTime: "08:20", endTime: "09:00" },
+    { number: 2, startTime: "09:10", endTime: "09:50" },
+    { number: 3, startTime: "10:10", endTime: "10:50" },
+    { number: 4, startTime: "11:00", endTime: "11:40" },
+    { number: 5, startTime: "13:50", endTime: "14:30" },
+    { number: 6, startTime: "14:40", endTime: "15:20" },
+    { number: 7, startTime: "15:40", endTime: "16:20" },
+    { number: 8, startTime: "16:30", endTime: "17:10" },
+    { number: 9, startTime: "18:40", endTime: "19:20" },
+    { number: 10, startTime: "19:30", endTime: "20:10" },
+    { number: 11, startTime: "20:20", endTime: "21:00" }
+];
+
+// 根据校区索引获取对应时间表
+function getTimeSlotsByAreaIndex(areaIndex) {
+    if (areaIndex === 0) return TimeSlots_one;
+    if (areaIndex === 1) return TimeSlots_two;
+    if (areaIndex === 2) return TimeSlots_three;
+    // 默认返回第一个
+    return TimeSlots_one;
+}
+
+async function importPresetTimeSlots(timeSlots) {
+    console.log(`JS: 准备导入 ${timeSlots.length} 个预设时间段。`);
+    if (timeSlots.length > 0) {
+        window.shiguangBridge.showToast(`正在导入 ${timeSlots.length} 个预设时间段...`);
+        try {
+            await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+            window.shiguangBridge.showToast("预设时间段导入成功!");
+        } catch (error) {
+            window.shiguangBridge.showToast("导入时间段失败: " + error.message);
+            console.error('JS: Save Time Slots Error:', error);
+        }
+    } else {
+        window.shiguangBridge.showToast("警告:时间段为空,未导入时间段信息。");
+        console.warn("JS: 警告:传入时间段为空,未导入时间段信息。");
+    }
+}
+
+// ===================== 主流程 =====================
+
+async function runImportFlow() {
+
+    const alertConfirmed = await promptUserToStart();
+    if (!alertConfirmed) {
+        window.shiguangBridge.showToast("用户取消了导入。");
+        console.log("JS: 用户取消了导入流程。");
+        return;
+    }
+
+    const academicYear = await getAcademicYear();
+    if (academicYear === null) {
+        window.shiguangBridge.showToast("导入已取消。");
+        console.log("JS: 获取学年失败/取消,流程终止。");
+        return;
+    }
+    console.log(`JS: 已选择学年: ${academicYear}`);
+
+    const semesterIndex = await selectSemester();
+    if (semesterIndex === null || isNaN(semesterIndex) || semesterIndex < 0) {
+        window.shiguangBridge.showToast("导入已取消。");
+        console.log("JS: 选择学期失败/取消,流程终止。");
+        return;
+    }
+    console.log(`JS: 已选择学期索引: ${semesterIndex}`);
+
+    const areaIndex = await selectArea();
+    if (areaIndex === null || isNaN(areaIndex) || areaIndex < 0) {
+        window.shiguangBridge.showToast("导入已取消。");
+        console.log("JS: 选择校区失败/取消,流程终止。");
+        return;
+    }
+    console.log(`JS: 已选择校区索引: ${areaIndex}`);
+
+    const startDate = await getSemesterStartDate();
+    console.log(`JS: 学期开始日期输入结果: ${startDate}`);
+
+    const result = await fetchAndParseCourses(academicYear, semesterIndex);
+    if (result === null) {
+        console.log("JS: 课程获取或解析失败,流程终止。");
+        return;
+    }
+
+    result.config.semesterStartDate = startDate;
+    const { courses, config } = result;
+
+    const saveResult = await saveCourses(courses);
+    if (!saveResult) {
+        console.log("JS: 课程保存失败,流程终止。");
+        return;
+    }
+
+    try {
+        await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
+        window.shiguangBridge.showToast(`课表配置更新成功!总周数:${config.semesterTotalWeeks}周。`);
+    } catch (error) {
+        window.shiguangBridge.showToast(`课表配置保存失败: ${error.message}`);
+        console.error('JS: Save Config Error:', error);
+    }
+
+    // 根据校区选择对应时间表并导入
+    const timeSlots = getTimeSlotsByAreaIndex(areaIndex);
+    await importPresetTimeSlots(timeSlots);
+
+    window.shiguangBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
+    console.log("JS: 整个导入流程执行完毕并成功。");
+    window.shiguangBridge.notifyTaskCompletion();
+}
+
+// 启动导入流程,并添加全局异常捕获
+runImportFlow().catch(e => {
+    console.error('JS: 导入流程异常:', e);
+    try {
+        window.shiguangBridge.showToast('导入失败:' + (e && e.message ? e.message : e));
+    } catch (_) {
+        // 忽略
+    }
+});