Przeglądaj źródła

add: 添加南昌航空大学课表导入适配 (#605)

Haooz 2 tygodni temu
rodzic
commit
a206a73934
3 zmienionych plików z 260 dodań i 1 usunięć
  1. 6 1
      index/root_index.yaml
  2. 9 0
      resources/NCHU/adapters.yaml
  3. 245 0
      resources/NCHU/nchu.js

+ 6 - 1
index/root_index.yaml

@@ -926,4 +926,9 @@ schools:
   - id: "NYNU"
     name: "南阳师范学院"
     initial: "N"
-    resource_folder: "NYNU"
+    resource_folder: "NYNU"
+
+  - id: "NCHU"
+    name: "南昌航空大学"
+    initial: "N"
+    resource_folder: "NCHU"

+ 9 - 0
resources/NCHU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/NCHU/adapters.yaml
+adapters:
+  - adapter_id: "NCHU_01"
+    adapter_name: "南昌航空大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "nchu.js"
+    import_url: "http://jwc-publish2.jwc.nchu.edu.cn/"
+    maintainer: "Haooz"
+    description: "南昌航空大学强智教务系统课表导入(需要校园网访问),进入课表查询界面以后,先点击查询所有全部周,再执行导入"

+ 245 - 0
resources/NCHU/nchu.js

@@ -0,0 +1,245 @@
+// 文件: nchu.js
+// 南昌航空大学教务系统课程表导入脚本
+
+function isOnSchedulePage() {
+    const url = window.location.href;
+    return /jwc-publish2\.jwc\.nchu\.edu\.cn/i.test(url);
+}
+
+// 解析周数字符串,支持逗号/顿号分隔的多段与连续区间
+// 例:第4-6,8-19周 / 第5,7,9,11,13,15,17,19周 / 第9周 / 第13-16周
+function parseWeeks(timeStr) {
+    const weeks = new Set();
+    const segment = timeStr.match(/第\s*([\d、,\-\s]+)\s*周/);
+    if (!segment) return [];
+    segment[1].split(/[,、\s]+/).forEach(part => {
+        part = part.trim();
+        if (!part) return;
+        const range = part.match(/^(\d+)\s*-\s*(\d+)$/);
+        if (range) {
+            const start = parseInt(range[1], 10);
+            const end = parseInt(range[2], 10);
+            for (let i = start; i <= end; i++) weeks.add(i);
+        } else if (/^\d+$/.test(part)) {
+            weeks.add(parseInt(part, 10));
+        }
+    });
+    return Array.from(weeks).sort((a, b) => a - b);
+}
+
+// 解析节次字符串,兼容 "01~02小节" 与 "01~02节" 两种写法
+function parseSections(sectionStr) {
+    const match = sectionStr.match(/(\d{1,2})~(\d{1,2})(?:小)?节/);
+    if (match) {
+        return { start: parseInt(match[1], 10), end: parseInt(match[2], 10) };
+    }
+    return null;
+}
+
+// 从文档中提取课程数据
+function extractCoursesFromDoc(doc) {
+    const courses = [];
+    
+    const table = doc.querySelector('.time-table table');
+    if (!table) {
+        console.log('未找到课表表格');
+        return courses;
+    }
+    
+    const rows = table.querySelectorAll('tbody tr');
+    
+    rows.forEach(row => {
+        const cells = row.querySelectorAll('td');
+        if (cells.length < 2) return;
+        
+        // cells[0] 是节次列,cells[1..] 对应周一~周日
+        for (let i = 1; i < cells.length; i++) {
+            const day = i;
+            const cell = cells[i];
+            
+            // 一个格子里可能有多门课(多个 .item-box)
+            const itemBoxes = cell.querySelectorAll('.item-box');
+            itemBoxes.forEach(itemBox => {
+                parseItemBox(itemBox, day, courses);
+            });
+        }
+    });
+    
+    return courses;
+}
+
+// 解析单个 .item-box(一门课),其内部可能含多个时间段块
+function parseItemBox(itemBox, day, courses) {
+    // 课程全名:.item-box 内第一个 <p>
+    const courseP = itemBox.querySelector('p');
+    const name = courseP ? courseP.textContent.trim() : '';
+    if (!name) return;
+    
+    // 每个时间段块的构成:<p>课程名</p><div class="tch-name">…</div><div><span item1>教室</span><span item3>周次</span></div>
+    itemBox.querySelectorAll('.tch-name').forEach(tch => {
+        const segDiv = tch.nextElementSibling;
+        if (!segDiv) return;
+        
+        // 周次:该 div 中 item3.png 所在 span 的文本(如 "第13-16周 星期四")
+        const weekImg = segDiv.querySelector('img[src*="item3.png"]');
+        const timeInfo = weekImg && weekImg.parentElement ? weekImg.parentElement.textContent.trim() : '';
+        const weeks = parseWeeks(timeInfo);
+        if (weeks.length === 0) return;
+        
+        // 节次:从 tch-name 内 "01~02节" 的 span 获取
+        let sectionInfo = null;
+        tch.querySelectorAll('span').forEach(s => {
+            if (sectionInfo) return;
+            sectionInfo = parseSections(s.textContent);
+        });
+        if (!sectionInfo) return;
+        
+        // 教室:该 div 中 item1.png 所在 span 的文字(如 "博学楼F栋-F302")
+        let room = '';
+        const roomImg = segDiv.querySelector('img[src*="item1.png"]');
+        if (roomImg && roomImg.parentElement) {
+            room = roomImg.parentElement.textContent.trim();
+        }
+        
+        // 教师:在本块 tch-name 的 span 中找 "教师:xxx"(避免与学分/节次拼接)
+        let teacher = '';
+        tch.querySelectorAll('span').forEach(s => {
+            if (teacher) return;
+            const m = s.textContent.match(/教师[::]\s*(.+)/);
+            if (m) teacher = m[1].trim();
+        });
+        
+        courses.push({
+            name,
+            teacher,
+            position: room || '未指定',
+            day,
+            startSection: sectionInfo.start,
+            endSection: sectionInfo.end,
+            weeks
+        });
+    });
+}
+
+// 获取当前页面的课程(兼容课表页直接注入,或从首页 iframe 中取课表内容)
+function getCurrentWeekCourses() {
+    // 若当前文档自身就是课表页,直接解析
+    if (document.querySelector('.time-table table')) {
+        return extractCoursesFromDoc(document);
+    }
+    
+    // 否则遍历 iframe 查找课表页
+    const iframes = Array.from(document.querySelectorAll('iframe'));
+    for (const iframe of iframes) {
+        try {
+            if (iframe.contentDocument && iframe.contentDocument.querySelector('.time-table table')) {
+                return extractCoursesFromDoc(iframe.contentDocument);
+            }
+        } catch (e) {
+            // 跨域 iframe 跳过
+        }
+    }
+    
+    return [];
+}
+
+// 去重合并课程
+function mergeAndDeduplicateCourses(allCourses) {
+    const courseMap = new Map();
+    
+    allCourses.forEach(course => {
+        const key = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.teacher}-${course.position}`;
+        
+        if (!courseMap.has(key)) {
+            courseMap.set(key, {
+                ...course,
+                weeks: [...course.weeks]
+            });
+        } else {
+            const existing = courseMap.get(key);
+            const weekSet = new Set([...existing.weeks, ...course.weeks]);
+            existing.weeks = Array.from(weekSet).sort((a, b) => a - b);
+        }
+    });
+    
+    return Array.from(courseMap.values());
+}
+
+// 生成时间段配置(该校实际为 11 节课)
+function generateTimeSlots() {
+    return [
+        { "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": "14:00", "endTime": "14:45" },
+        { "number": 6, "startTime": "14:55", "endTime": "15:40" },
+        { "number": 7, "startTime": "16:00", "endTime": "16:45" },
+        { "number": 8, "startTime": "16:55", "endTime": "17:40" },
+        { "number": 9, "startTime": "19:00", "endTime": "19:45" },
+        { "number": 10, "startTime": "19:55", "endTime": "20:40" },
+        { "number": 11, "startTime": "20:50", "endTime": "21:35" }
+    ];
+}
+
+// 主函数:导入课程
+async function importCourseSchedule() {
+    try {
+        console.log('开始导入课程表...');
+        window.shiguangBridge.showToast('正在获取课表数据...');
+        
+        // 获取当前周课程
+        const courses = getCurrentWeekCourses();
+        console.log(`找到 ${courses.length} 门课程`);
+        
+        if (courses.length === 0) {
+            window.shiguangBridge.showToast('未找到课程数据');
+            return false;
+        }
+        
+        console.log('课程数据:', courses);
+        
+        // 导入课程
+        const coursesResult = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (coursesResult === true) {
+            console.log('课程导入成功!');
+            window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
+        } else {
+            console.log('课程导入失败');
+            window.shiguangBridge.showToast('课程导入失败');
+            return false;
+        }
+        
+        // 生成并导入时间段
+        const finalTimeSlots = generateTimeSlots();
+        const timeSlotsResult = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(finalTimeSlots));
+        if (timeSlotsResult === true) {
+            console.log('时间段导入成功!');
+        }
+        
+        return true;
+        
+    } catch (error) {
+        console.error('导入过程出错:', error);
+        window.shiguangBridge.showToast('导入失败: ' + error.message);
+        return false;
+    }
+}
+
+// ========== 主执行逻辑 ==========
+
+if (isOnSchedulePage()) {
+    console.log('检测到南昌航空大学教务系统');
+    window.shiguangBridge.showToast('正在准备导入课程表...');
+    
+    setTimeout(async () => {
+        const success = await importCourseSchedule();
+        if (success) {
+            window.shiguangBridge.notifyTaskCompletion();
+        }
+    }, 2000);
+    
+} else {
+    console.log('当前不在教务系统页面');
+    window.shiguangBridge.showToast('请先登录教务系统!');
+}