ソースを参照

add: 华南理工大学教务适配

dezige131 1 週間 前
コミット
a1064c0d4e
3 ファイル変更279 行追加1 行削除
  1. 6 1
      index/root_index.yaml
  2. 264 0
      resources/SCUT/SCUT_01.js
  3. 9 0
      resources/SCUT/adapters.yaml

+ 6 - 1
index/root_index.yaml

@@ -261,4 +261,9 @@ schools:
   - id: "TCU"
     name: "天津城建大学"
     initial: "T"
-    resource_folder: "TCU"
+    resource_folder: "TCU"
+
+  - id: "SCUT"
+    name: "华南理工大学"
+    initial: "H"
+    resource_folder: "SCUT"

+ 264 - 0
resources/SCUT/SCUT_01.js

@@ -0,0 +1,264 @@
+/**
+ * 解析正方教务系统的周次字符串
+ */
+function parseZhengFangWeeks(weekStr) {
+    if (!weekStr) return [];
+    let weeks = [];
+    let isOdd = weekStr.includes('单');
+    let isEven = weekStr.includes('双');
+    let cleanStr = weekStr.replace(/周|\(单\)|\(双\)|单|双/g, '').replace(/\s+/g, '');
+
+    let parts = cleanStr.split(',');
+    for (let part of parts) {
+        if (part.includes('-')) {
+            let [start, end] = part.split('-');
+            for (let i = parseInt(start); i <= parseInt(end); i++) {
+                if (isOdd && i % 2 === 0) continue;
+                if (isEven && i % 2 !== 0) continue;
+                if (!weeks.includes(i)) weeks.push(i);
+            }
+        } else {
+            let w = parseInt(part);
+            if (!isNaN(w) && !weeks.includes(w)) {
+                if (isOdd && w % 2 === 0) continue;
+                if (isEven && w % 2 !== 0) continue;
+                weeks.push(w);
+            }
+        }
+    }
+    return weeks.sort((a, b) => a - b);
+}
+
+/**
+ * 封装兼容 WebVPN 的原生 AJAX 请求
+ */
+function requestData(url, method = 'GET', data = null) {
+    return new Promise((resolve, reject) => {
+        let xhr = new XMLHttpRequest();
+        xhr.open(method, url, true);
+        if (method === 'POST') {
+            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
+            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+        }
+        xhr.onreadystatechange = function() {
+            if (xhr.readyState === 4) {
+                if (xhr.status >= 200 && xhr.status < 300) {
+                    resolve(xhr.responseText);
+                } else {
+                    reject(new Error("网络请求失败,状态码:" + xhr.status));
+                }
+            }
+        };
+        xhr.onerror = function() {
+            reject(new Error("网络请求发生错误,请检查网络连接。"));
+        };
+        xhr.send(data);
+    });
+}
+
+/**
+ * 用正则从 HTML 文本中硬核提取 select 选项
+ */
+function extractOptionsByRegex(html, selectId) {
+    let selectRegex = new RegExp(`<select[^>]*id="${selectId}"[^>]*>(.*?)<\/select>`, 'is');
+    let selectMatch = html.match(selectRegex);
+    if (!selectMatch) return [];
+    
+    let optionsHtml = selectMatch[1];
+    let optionRegex = /<option\s+value="([^"]+)"([^>]*)>([^<]+)<\/option>/gi;
+    let options = [];
+    let match;
+    while ((match = optionRegex.exec(optionsHtml)) !== null) {
+        if (match[1].trim() !== "") {
+            options.push({
+                value: match[1].trim(),
+                text: match[3].trim(),
+                selected: match[2].includes('selected')
+            });
+        }
+    }
+    return options;
+}
+
+/**
+ * 异步编排流程
+ */
+async function runImportFlow() {
+    try {
+        if (typeof window.AndroidBridge !== 'undefined') {
+            AndroidBridge.showToast("正在通过 WebVPN 通道读取数据...");
+        } else {
+            console.log("【1/4】正在获取学期选项...");
+        }
+
+        let sysPath = typeof _path !== 'undefined' ? _path : '/jwglxt';
+        let semesters = [];
+        let semesterValues = [];
+        let defaultIndex = 0;
+
+        // 1. 尝试直接通过正则表达式,去后台请求并提取页面中的下拉框
+        console.log("正在后台请求页面源码...");
+        const indexHtml = await requestData(`${sysPath}/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default`, 'GET');
+        
+        let yearOpts = extractOptionsByRegex(indexHtml, 'xnm');
+        let termOpts = extractOptionsByRegex(indexHtml, 'xqm');
+
+        if (yearOpts.length === 0 || termOpts.length === 0) {
+            throw new Error("源码提取学期信息失败,可能是 WebVPN 会话过期,请重新登录教务系统!");
+        }
+
+        let count = 0;
+        yearOpts.forEach(y => {
+            termOpts.forEach(t => {
+                semesters.push(`${y.text} 第${t.text}学期`);
+                semesterValues.push({ xnm: y.value, xqm: t.value });
+                if (y.selected && t.selected) {
+                    defaultIndex = count;
+                }
+                count++;
+            });
+        });
+
+        // 2. 弹出选择框
+        let selectedIdx = defaultIndex;
+        if (typeof window.AndroidBridgePromise !== 'undefined') {
+            let userChoice = await window.AndroidBridgePromise.showSingleSelection(
+                "请选择要导入的学期", 
+                JSON.stringify(semesters), 
+                defaultIndex
+            );
+            if (userChoice === null) {
+                AndroidBridge.showToast("已取消导入");
+                return;
+            }
+            selectedIdx = userChoice;
+        } else {
+            let msg = "【浏览器测试】请选择学期对应的序号:\n\n";
+            semesters.forEach((s, idx) => {
+                if (Math.abs(idx - defaultIndex) <= 4) msg += `[ ${idx} ] : ${s}\n`;
+            });
+            msg += "\n请输入方括号中的数字:";
+            let userInput = prompt(msg, defaultIndex);
+            if (userInput === null) return;
+            selectedIdx = parseInt(userInput);
+            if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= semesters.length) {
+                alert("输入无效,使用默认学期!");
+                selectedIdx = defaultIndex;
+            }
+        }
+
+        const targetData = semesterValues[selectedIdx];
+        if (typeof window.AndroidBridge !== 'undefined') {
+            AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 数据...`);
+        } else {
+            console.log(`【2/4】正在向服务器请求 [${semesters[selectedIdx]}] 的 JSON 数据...`);
+        }
+
+        // 3. 发送 Ajax 请求获取真实的课表和作息时间
+        const postBody = `xnm=${targetData.xnm}&xqm=${targetData.xqm}&kzlx=ck&xsdm=&kclbdm=&kclxdm=`;
+        
+        const [kbResText, timeResText] = await Promise.all([
+            requestData(`${sysPath}/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151`, 'POST', postBody),
+            requestData(`${sysPath}/kbcx/xskbcx_cxRjc.html?gnmkdm=N2151`, 'POST', postBody)
+        ]);
+
+        const kbJson = JSON.parse(kbResText);
+        const timeJson = JSON.parse(timeResText);
+
+        // 4. 解析作息时间 (拦截 21:35 之后的时间)
+        let timeSlots = [];
+        if (Array.isArray(timeJson)) {
+            timeJson.forEach(t => {
+                if (t.jssj > "21:35") return; 
+                timeSlots.push({
+                    number: parseInt(t.jcmc, 10),
+                    startTime: t.qssj,
+                    endTime: t.jssj
+                });
+            });
+            timeSlots.sort((a, b) => a.number - b.number);
+        }
+
+        // 5. 解析课程数据
+        let parsedCourses = [];
+        if (kbJson && kbJson.kbList) {
+            kbJson.kbList.forEach(c => {
+                let courseObj = {
+                    name: c.kcmc || "未知课程",
+                    teacher: c.xm || "未知",
+                    position: c.cdmc || "待定",
+                    day: parseInt(c.xqj),
+                    isCustomTime: false
+                };
+
+                courseObj.weeks = parseZhengFangWeeks(c.zcd);
+
+                if (c.jcs) {
+                    let secParts = c.jcs.split('-');
+                    courseObj.startSection = parseInt(secParts[0]);
+                    courseObj.endSection = parseInt(secParts[secParts.length - 1] || secParts[0]);
+                }
+
+                if (courseObj.name && courseObj.weeks.length > 0 && courseObj.startSection) {
+                    parsedCourses.push(courseObj);
+                }
+            });
+        }
+
+        if (parsedCourses.length === 0) {
+            const errMsg = "该学期暂无排课数据。";
+            if (typeof window.AndroidBridgePromise !== 'undefined') {
+                await window.AndroidBridgePromise.showAlert("提示", errMsg, "好的");
+            } else alert(errMsg);
+            return;
+        }
+
+        // 6. 去重
+        let uniqueCourses = [];
+        let courseSet = new Set();
+        parsedCourses.forEach(course => {
+            let uniqueKey = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.weeks.join(',')}`;
+            if (!courseSet.has(uniqueKey)) {
+                courseSet.add(uniqueKey);
+                uniqueCourses.push(course);
+            }
+        });
+
+        const config = {
+            "defaultClassDuration": 45,
+            "defaultBreakDuration": 5
+        };
+
+        // 7. 打印并保存
+        if (typeof window.AndroidBridgePromise === 'undefined') {
+            console.log("【测试成功】被 21:35 规则过滤后的作息时间表:\n", timeSlots);
+            console.log(`【测试成功】共获取到 ${uniqueCourses.length} 门课程:\n`, JSON.stringify(uniqueCourses, null, 2));
+            alert(`解析成功!获取到 ${uniqueCourses.length} 门课程及过滤后的作息时间。\n请打开F12控制台查看。`);
+            return;
+        }
+
+        await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
+        if (timeSlots.length > 0) {
+            await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+        }
+        
+        const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(uniqueCourses));
+        if (!saveResult) {
+            AndroidBridge.showToast("保存失败,请重试!");
+            return;
+        }
+
+        AndroidBridge.showToast(`成功导入 ${uniqueCourses.length} 节课程!`);
+        AndroidBridge.notifyTaskCompletion();
+
+    } catch (error) {
+        if (typeof window.AndroidBridge !== 'undefined') {
+            AndroidBridge.showToast("导入异常: " + error.message);
+        } else {
+            console.error("【导入异常】", error);
+            alert("导入异常: " + error.message);
+        }
+    }
+}
+
+runImportFlow();

+ 9 - 0
resources/SCUT/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/SCUT/adapters.yaml
+adapters:
+  - adapter_id: "SCUT_01"
+    adapter_name: "华南理工大学适配教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "SCUT_01.js"
+    import_url: "https://xsjw2018-jw.webvpn.scut.edu.cn/"
+    maintainer: "dezige131"
+    description: "华南理工大学适配教务,非本校开发者适配如果有误建议提交issues"