Selaa lähdekoodia

add: 湖南科技学院内网教务适配

zknb-666 1 viikko sitten
vanhempi
sitoutus
be4e08826f
3 muutettua tiedostoa jossa 282 lisäystä ja 0 poistoa
  1. 5 0
      index/root_index.yaml
  2. 268 0
      resources/HUSE/HUSE.js
  3. 9 0
      resources/HUSE/adapters.yaml

+ 5 - 0
index/root_index.yaml

@@ -168,6 +168,11 @@ schools:
     initial: "H"
     resource_folder: "HNUST"
 
+  - id: "HUSE"
+    name: "湖南科技学院"
+    initial: "H"
+    resource_folder: "HUSE"
+
   - id: "JSNU"
     name: "江苏师范大学"
     initial: "J"

+ 268 - 0
resources/HUSE/HUSE.js

@@ -0,0 +1,268 @@
+/**
+ * 将周次字符串(如 "1-4,6,8-10")解析为有序的周次数字数组。
+ * 支持连续区间(如 "1-4")和单个周次(如 "6")的混合格式。
+ * @param {string} weekStr - 原始周次字符串
+ * @returns {number[]} 去重并升序排列的周次数组
+ */
+function parseWeeks(weekStr) {
+    const weeks = [];
+    const parts = weekStr.split(',');
+
+    for (const part of parts) {
+        if (part.includes('-')) {
+            const [start, end] = part.split('-');
+            for (let w = parseInt(start); w <= parseInt(end); w++) {
+                if (!weeks.includes(w)) weeks.push(w);
+            }
+        } else {
+            const w = parseInt(part);
+            if (!isNaN(w) && !weeks.includes(w)) weeks.push(w);
+        }
+    }
+
+    return weeks.sort((a, b) => a - b);
+}
+
+/**
+ * 从教务系统返回的 HTML 文档中解析所有课程信息。
+ * 遍历课表格 #timetable 中每个单元格,提取课程名、教师、教室及周次节次。
+ * @param {Document} doc - 已解析的 HTML 文档对象
+ * @returns {object[]} 课程对象数组,每项包含 day/name/teacher/position/weeks/startSection/endSection
+ */
+function extractCoursesFromDoc(doc) {
+    const courses = [];
+    const table = doc.getElementById('timetable');
+    if (!table) throw new Error("未找到课表格元素(#timetable),请确认教务系统页面已正常加载。");
+
+    const rows = table.getElementsByTagName('tr');
+    // 跳过首行(表头)和末行(通常为空白行)
+    for (let rowIdx = 1; rowIdx < rows.length - 1; rowIdx++) {
+        const cells = rows[rowIdx].getElementsByTagName('td');
+
+        for (let colIdx = 0; colIdx < cells.length; colIdx++) {
+            const dayOfWeek = colIdx + 1; // 列索引对应星期几(1=周一)
+            const cell = cells[colIdx];
+
+            const contentDivs = cell.querySelectorAll('div.kbcontent');
+            if (contentDivs.length === 0) continue;
+
+            contentDivs.forEach(div => {
+                const rawHtml = div.innerHTML;
+                // 跳过空单元格
+                if (!rawHtml.trim() || rawHtml === '&nbsp;') return;
+
+                // 同一格内多门课以 10 个以上连字符分隔
+                const blocks = rawHtml.split(/-{10,}\s*<br\s*\/?>/i);
+
+                blocks.forEach(block => {
+                    if (!block.trim()) return;
+
+                    const tmp = document.createElement('div');
+                    tmp.innerHTML = block;
+
+                    const course = {
+                        day: dayOfWeek,
+                        isCustomTime: false
+                    };
+
+                    // 课程名称取第一个文本节点,fallback 到 innerText 首行
+                    const firstNode = tmp.childNodes[0];
+                    if (firstNode && firstNode.nodeType === Node.TEXT_NODE) {
+                        course.name = firstNode.nodeValue.trim();
+                    } else {
+                        course.name = tmp.innerText.split('\n')[0].trim();
+                    }
+
+                    // 教师姓名
+                    const teacherEl = tmp.querySelector('font[title="教师"]');
+                    course.teacher = teacherEl ? teacherEl.innerText.trim() : "未知";
+
+                    // 上课教室
+                    const roomEl = tmp.querySelector('font[title="教室"]');
+                    course.position = roomEl ? roomEl.innerText.trim() : "待定";
+
+                    // 周次与节次:格式为 "X-Y(周)[A-B节]" 或仅 "X-Y(周)"
+                    const timeEl = tmp.querySelector('font[title="周次(节次)"]');
+                    if (!timeEl) return;
+
+                    const timeText = timeEl.innerText.trim();
+                    const fullMatch = timeText.match(/(.+?)\(周\)\[(\d+)-(\d+)节\]/);
+                    if (fullMatch) {
+                        course.weeks = parseWeeks(fullMatch[1]);
+                        course.startSection = parseInt(fullMatch[2]);
+                        course.endSection = parseInt(fullMatch[3]);
+                    } else {
+                        const weekOnlyMatch = timeText.match(/(.+?)\(周\)/);
+                        if (weekOnlyMatch) {
+                            // 节次信息缺失时,根据行索引推算默认节次
+                            course.weeks = parseWeeks(weekOnlyMatch[1]);
+                            course.startSection = rowIdx * 2 - 1;
+                            course.endSection = rowIdx * 2;
+                        } else {
+                            return; // 无法识别的时间格式,跳过
+                        }
+                    }
+
+                    if (course.name && course.weeks && course.weeks.length > 0) {
+                        courses.push(course);
+                    }
+                });
+            });
+        }
+    }
+
+    return courses;
+}
+
+/**
+ * 根据当前日期返回对应学期的作息时间表。
+ * 5月1日至9月30日期间使用夏季作息,其余时间使用春秋冬季作息。
+ * @returns {object[]} 节次时间数组,每项包含 number/startTime/endTime
+ */
+function getPresetTimeSlots() {
+    const now = new Date();
+    const month = now.getMonth() + 1;
+    const day = now.getDate();
+    // 判断是否处于夏季作息期间(5月1日 ~ 9月30日)
+    const isSummer = (month > 5) || (month === 5 && day >= 1) || (month < 10) && (month > 4);
+
+    if (isSummer) {
+        // 夏季作息
+        return [
+            { number: 1,  startTime: "08:10", endTime: "08:55" },
+            { number: 2,  startTime: "09:05", endTime: "09:50" },
+            { number: 3,  startTime: "10:10", endTime: "10:55" },
+            { number: 4,  startTime: "11:05", endTime: "11:50" },
+            { number: 5,  startTime: "14:45", endTime: "15:30" },
+            { number: 6,  startTime: "15:40", endTime: "16:25" },
+            { number: 7,  startTime: "16:40", endTime: "17:25" },
+            { number: 8,  startTime: "17:35", endTime: "18:20" },
+            { number: 9,  startTime: "19:30", endTime: "20:15" },
+            { number: 10, startTime: "20:25", endTime: "21:10" },
+            { number: 11, startTime: "21:20", endTime: "22:05" }
+        ];
+    } else {
+        // 春秋冬季作息
+        return [
+            { number: 1,  startTime: "08:20", endTime: "09:05" },
+            { number: 2,  startTime: "09:05", endTime: "10:00" },
+            { number: 3,  startTime: "10:20", endTime: "11:05" },
+            { number: 4,  startTime: "11:15", endTime: "12:00" },
+            { number: 5,  startTime: "14:30", endTime: "15:15" },
+            { number: 6,  startTime: "15:25", endTime: "16:10" },
+            { number: 7,  startTime: "16:25", endTime: "17:10" },
+            { number: 8,  startTime: "17:20", endTime: "18:05" },
+            { number: 9,  startTime: "19:10", endTime: "19:55" },
+            { number: 10, startTime: "20:05", endTime: "20:50" },
+            { number: 11, startTime: "21:00", endTime: "21:45" }
+        ];
+    }
+}
+
+/**
+ * 返回全局课表基础配置(单节课时长与课间休息时长)。
+ * @returns {{ defaultClassDuration: number, defaultBreakDuration: number }}
+ */
+function getCourseConfig() {
+    return {
+        defaultClassDuration: 45,
+        defaultBreakDuration: 10
+    };
+}
+
+/**
+ * 课表导入主流程。
+ * 依次完成:发起请求 → 解析 HTML → 提取课程 → 保存配置/作息/课程 → 通知完成。
+ * 在浏览器调试环境中仅打印结果,不调用 AndroidBridge。
+ */
+async function runImportFlow() {
+    const isApp = typeof window.AndroidBridgePromise !== 'undefined';
+    const hasToast = typeof window.AndroidBridge !== 'undefined';
+
+    try {
+        if (hasToast) {
+            AndroidBridge.showToast("正在拉取课表,请稍候...");
+        } else {
+            console.log("[HUSE] 开始请求课表页面...");
+        }
+
+        const response = await fetch('/jsxsd/xskb/xskb_list.do', { method: 'GET' });
+        const htmlText = await response.text();
+        const doc = new DOMParser().parseFromString(htmlText, 'text/html');
+
+        // 读取学期列表(当前仅用于记录,实际取最新学期)
+        const selectEl = doc.getElementById('xnxq01id');
+        const semesters = [];
+        const semesterValues = [];
+        let defaultIndex = 0;
+
+        if (selectEl) {
+            selectEl.querySelectorAll('option').forEach((opt, idx) => {
+                semesters.push(opt.innerText.trim());
+                semesterValues.push(opt.value);
+                if (opt.hasAttribute('selected')) defaultIndex = idx;
+            });
+        }
+        // 始终选取列表末尾的最新学期
+        defaultIndex = semesters.length - 1;
+        console.log(`[HUSE] 共找到 ${semesters.length} 个学期,当前使用:${semesters[defaultIndex] || '未知'}`);
+
+        const courses = extractCoursesFromDoc(doc);
+
+        if (courses.length === 0) {
+            const msg = "未解析到任何课程,当前学期可能暂无排课。";
+            console.warn("[HUSE] " + msg);
+            if (isApp) {
+                await window.AndroidBridgePromise.showAlert("提示", msg, "好的");
+            } else {
+                alert(msg);
+            }
+            return;
+        }
+
+        console.log(`[HUSE] 成功解析 ${courses.length} 门课程。`);
+
+        const config = getCourseConfig();
+        const timeSlots = getPresetTimeSlots();
+
+        // 浏览器调试环境:输出结果后退出,不执行 APP 存储逻辑
+        if (!isApp) {
+            console.log("[HUSE] 课表基础配置:", config);
+            console.log("[HUSE] 作息时间表:", timeSlots);
+            console.log("[HUSE] 课程列表:", courses);
+            alert(`解析完成!共获取 ${courses.length} 门课程及作息时间,详情见控制台(F12)。`);
+            return;
+        }
+
+        // APP 环境:保存课表配置与作息时间
+        const configSaved = await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
+        const slotsSaved = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+        if (!configSaved || !slotsSaved) {
+            // 时间配置保存失败不强制中断,继续尝试导入课程
+            console.warn("[HUSE] 课表时间配置保存失败,将继续尝试导入课程。");
+            AndroidBridge.showToast("时间配置保存失败,继续导入课程...");
+        }
+
+        // APP 环境:保存课程数据
+        const courseSaved = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (!courseSaved) {
+            console.error("[HUSE] 课程数据保存失败。");
+            AndroidBridge.showToast("课程保存失败,请重试!");
+            return;
+        }
+
+        console.log(`[HUSE] 导入完成,共写入 ${courses.length} 门课程。`);
+        AndroidBridge.showToast(`成功导入 ${courses.length} 门课程及作息时间!`);
+        AndroidBridge.notifyTaskCompletion();
+
+    } catch (err) {
+        console.error("[HUSE] 导入流程发生异常:", err);
+        if (hasToast) {
+            AndroidBridge.showToast("导入失败:" + err.message);
+        } else {
+            alert("导入失败:" + err.message);
+        }
+    }
+}
+
+runImportFlow();

+ 9 - 0
resources/HUSE/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/HUSE/adapters.yaml
+adapters:
+  - adapter_id: "HUSE"
+    adapter_name: "湖南科技学院"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "HUSE.js"
+    import_url: "http://jw.huse.cn/jsxsd"
+    maintainer: "KevinZ"
+    description: "湖南科技学院适配教务,如有错误请联系作者QQ:2865467965"