Просмотр исходного кода

Merge pull request #247 from LittleArray/main

add: 广东轻工职业技术大学(GDIPU)教务系统适配
星河欲转 2 недель назад
Родитель
Сommit
87cdbe1c0e
3 измененных файлов с 419 добавлено и 0 удалено
  1. 5 0
      index/root_index.yaml
  2. 9 0
      resources/GDIPU/adapters.yaml
  3. 405 0
      resources/GDIPU/gdipu.js

+ 5 - 0
index/root_index.yaml

@@ -406,3 +406,8 @@ schools:
     name: "泰山学院"
     initial: "T"
     resource_folder: "TSU"
+
+  - id: "GDIPU"
+    name: "广东轻工职业技术大学"
+    initial: "G"
+    resource_folder: "GDIPU"

+ 9 - 0
resources/GDIPU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/GDIPU/adapters.yaml
+adapters:
+  - adapter_id: "GDIPU_01"
+    adapter_name: "广东轻工职业技术大学教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "gdipu.js"
+    import_url: "https://jw.gdipu.edu.cn"
+    maintainer: "LittleArray"
+    description: "广东轻工职业技术大学(南海北区)适配教务,适配如果有误建议提交issues"

+ 405 - 0
resources/GDIPU/gdipu.js

@@ -0,0 +1,405 @@
+// 广东轻工职业技术大学教务适配器
+// 适配器ID: GDIPU_01
+
+// 提示用户输入学期开始日期
+const promptForStartDate = async () => {
+    try {
+        const today = new Date();
+        const defaultDate = today.toISOString().split('T')[0];
+        
+        const startDate = await window.AndroidBridgePromise.showPrompt(
+            "请输入学期开始日期",
+            "格式:YYYY-MM-DD(例如:2026-02-24)",
+            defaultDate,
+            "validateDate"
+        );
+        
+        if (startDate === null) {
+            throw new Error("用户取消了输入");
+        }
+        
+        if (!/^\d{4}-\d{2}-\d{2}$/.test(startDate)) {
+            AndroidBridge.showToast("日期格式不正确,请使用YYYY-MM-DD格式");
+            throw new Error("日期格式不正确");
+        }
+        
+        return startDate;
+    } catch (error) {
+        console.error("获取开始日期失败:", error);
+        throw error;
+    }
+};
+
+// 日期验证函数(供AndroidBridge调用)
+function validateDate(dateStr) {
+    if (!dateStr) {
+        return "日期不能为空";
+    }
+    
+    if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
+        return "日期格式不正确,请使用YYYY-MM-DD格式";
+    }
+    
+    const date = new Date(dateStr);
+    if (isNaN(date.getTime())) {
+        return "无效的日期";
+    }
+    
+    return false;
+}
+
+// 获取课程表数据
+const fetchTimetable = async (date) => {
+    try {
+        const response = await fetch('https://jw.gdipu.edu.cn/jsxsd/framework/main_index_loadkb.jsp', {
+            method: 'POST',
+            headers: {
+                'Accept': 'text/html, */*; q=0.01',
+                'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
+                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+                'Origin': 'https://jw.gdipu.edu.cn',
+                'Referer': window.location.href,
+                'X-Requested-With': 'XMLHttpRequest'
+            },
+            credentials: 'include',
+            body: `rq=${date}`
+        });
+        
+        if (!response.ok) {
+            throw new Error(`HTTP错误: ${response.status}`);
+        }
+        
+        const html = await response.text();
+        
+        // 检查是否有课程
+        const hasCourses = checkIfHasCourses(html);
+        
+        return { html, hasCourses };
+    } catch (error) {
+        console.error('获取课程表失败:', error);
+        throw error;
+    }
+};
+
+// 检查HTML中是否有课程
+const checkIfHasCourses = (html) => {
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(html, 'text/html');
+    
+    const table = doc.querySelector('form table.kb_table');
+    if (!table) {
+        return false;
+    }
+    
+    const courseCells = table.querySelectorAll('td p[title]');
+    return courseCells.length > 0;
+};
+
+// 从HTML字符串解析课程表
+const parseTimetableFromHTML = (html, date) => {
+    const courses = [];
+    
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(html, 'text/html');
+    
+    const table = doc.querySelector('form table.kb_table');
+    if (!table) {
+        console.error('未找到课程表表格');
+        return courses;
+    }
+    
+    const rows = table.querySelectorAll('tbody tr');
+    
+    rows.forEach((row) => {
+        const sectionCell = row.querySelector('td:first-child');
+        if (!sectionCell) return;
+        
+        const sectionText = sectionCell.textContent.trim().split('\n')[0];
+        const sectionInfo = parseSection(sectionText);
+        
+        // 遍历星期几的列(从第2列到第8列)
+        for (let colIndex = 1; colIndex <= 7; colIndex++) {
+            const dayCell = row.querySelector(`td:nth-child(${colIndex + 1})`);
+            if (!dayCell) continue;
+            
+            const courseP = dayCell.querySelector('p[title]');
+            if (!courseP) continue;
+            
+            const title = courseP.getAttribute('title');
+            const courseInfo = parseCourseFromTitle(title);
+            
+            const timeInfo = parseTimeInfo(courseInfo.time || '');
+            
+            let dayOfWeek;
+            if (timeInfo.day && timeInfo.day > 0) {
+                dayOfWeek = timeInfo.day;
+            } else {
+                // 列索引映射:1->星期一(1), 2->星期二(2), 3->星期三(3), 4->星期四(4), 5->星期五(5), 6->星期六(6), 7->星期日(7)
+                dayOfWeek = colIndex;
+            }
+            
+            // 将跨节课程拆分为多个单节课程
+            const startSection = sectionInfo.startSection;
+            const endSection = sectionInfo.endSection;
+            
+            for (let section = startSection; section <= endSection; section++) {
+                const course = {
+                    name: courseInfo.name || '',
+                    teacher: '', // 这个系统似乎没有教师信息
+                    position: courseInfo.location || '',
+                    day: dayOfWeek,
+                    startSection: section,
+                    endSection: section, // 单节课,开始和结束节次相同
+                    weeks: timeInfo.weeks.length > 0 ? timeInfo.weeks : [1] // 暂时使用第1周
+                };
+                
+                courses.push(course);
+            }
+        }
+    });
+    
+    return courses;
+};
+
+// 从title属性解析课程信息
+const parseCourseFromTitle = (title) => {
+    const info = {};
+    
+    const creditMatch = title.match(/课程学分:([\d.]+)/);
+    const propertyMatch = title.match(/课程属性:([^<]+)/);
+    const nameMatch = title.match(/课程名称:([^<]+)/);
+    const timeMatch = title.match(/上课时间:([^<]+)/);
+    const locationMatch = title.match(/上课地点:([^<]+)/);
+    const campusMatch = title.match(/上课校区:([^<]+)/);
+    const groupMatch = title.match(/分组名:([^<]+)/);
+    
+    if (creditMatch) info.credit = creditMatch[1];
+    if (propertyMatch) info.property = propertyMatch[1].trim();
+    if (nameMatch) info.name = nameMatch[1].trim();
+    if (timeMatch) info.time = timeMatch[1].trim();
+    if (locationMatch) info.location = locationMatch[1].trim();
+    if (campusMatch) info.campus = campusMatch[1].trim();
+    if (groupMatch) info.group = groupMatch[1].trim();
+    
+    return info;
+};
+
+// 解析上课时间字符串
+const parseTimeInfo = (timeStr) => {
+    const result = {
+        weeks: [],
+        day: 0,
+        startSection: 0,
+        endSection: 0
+    };
+    
+    // 解析周数范围
+    const weekMatch = timeStr.match(/第(\d+)(?:-(\d+))?周/);
+    if (weekMatch) {
+        const startWeek = parseInt(weekMatch[1], 10);
+        if (weekMatch[2]) {
+            const endWeek = parseInt(weekMatch[2], 10);
+            for (let week = startWeek; week <= endWeek; week++) {
+                result.weeks.push(week);
+            }
+        } else {
+            result.weeks.push(startWeek);
+        }
+    }
+    
+    // 解析星期几
+    const dayMap = {
+        '星期一': 1,
+        '星期二': 2,
+        '星期三': 3,
+        '星期四': 4,
+        '星期五': 5,
+        '星期六': 6,
+        '星期日': 7
+    };
+    
+    for (const [dayStr, dayNum] of Object.entries(dayMap)) {
+        if (timeStr.includes(dayStr)) {
+            result.day = dayNum;
+            break;
+        }
+    }
+    
+    // 解析节次
+    const sectionMatch = timeStr.match(/\[(\d+)-(\d+)\]/);
+    if (sectionMatch) {
+        result.startSection = parseInt(sectionMatch[1], 10);
+        result.endSection = parseInt(sectionMatch[2], 10);
+    }
+    
+    return result;
+};
+
+// 解析节次字符串
+const parseSection = (sectionStr) => {
+    const result = {
+        startSection: 0,
+        endSection: 0
+    };
+    
+    if (sectionStr.includes('-')) {
+        const parts = sectionStr.split('-');
+        result.startSection = parseInt(parts[0], 10);
+        result.endSection = parseInt(parts[1], 10);
+    } else {
+        result.startSection = parseInt(sectionStr, 10);
+        result.endSection = parseInt(sectionStr, 10);
+    }
+    
+    return result;
+};
+
+// 获取学期配置
+const getSemesterConfig = (startDate, totalWeeks) => {
+    return {
+        semesterStartDate: startDate,
+        totalWeeks: totalWeeks
+    };
+};
+
+// 获取时间段配置
+const getTimeSlots = (html) => {
+    // 北区有14节课,使用准确时间配置
+    return [
+        { number: 1, startTime: "08:30", endTime: "09:10" },
+        { number: 2, startTime: "09:15", endTime: "09:55" },
+        { number: 3, startTime: "10:15", endTime: "10:55" },
+        { number: 4, startTime: "11:00", endTime: "11:40" },
+        { number: 5, startTime: "11:45", endTime: "12:25" },
+        { number: 6, startTime: "13:15", endTime: "13:55" },
+        { number: 7, startTime: "14:00", endTime: "14:40" },
+        { number: 8, startTime: "14:45", endTime: "15:25" },
+        { number: 9, startTime: "15:45", endTime: "16:25" },
+        { number: 10, startTime: "16:30", endTime: "17:10" },
+        { number: 11, startTime: "17:15", endTime: "17:55" },
+        { number: 12, startTime: "19:30", endTime: "20:10" },
+        { number: 13, startTime: "20:15", endTime: "20:55" },
+        { number: 14, startTime: "21:00", endTime: "21:40" },
+    ];
+};
+
+// 保存课程数据
+const saveSchedule = async (courses, courseConfig, timeSlots) => {
+    try {
+        await Promise.allSettled([
+            window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(courseConfig)),
+            window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses)),
+            window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots))
+        ]);
+        
+        AndroidBridge.showToast("课程表导入成功!");
+        return true;
+    } catch (error) {
+        console.error("保存课程数据时出错:", error);
+        AndroidBridge.showToast("课程表导入失败:" + error.message);
+        return false;
+    }
+};
+
+// 日期增加指定天数
+const addDays = (dateStr, days) => {
+    const date = new Date(dateStr);
+    date.setDate(date.getDate() + days);
+    return date.toISOString().split('T')[0];
+};
+
+// 判断日期是否是周日(0=周日,1=周一,...,6=周六)
+const isSunday = (dateStr) => {
+    const date = new Date(dateStr);
+    return date.getDay() === 0; // 0表示周日
+};
+
+// 获取多周课程表
+const fetchMultiWeekTimetable = async (startDate) => {
+    const allCourses = [];
+    
+    //如果起始日期是周日,请求日期就-1
+    let requestDate = startDate;
+    if (isSunday(startDate)) {
+        requestDate = addDays(startDate, -1);
+    }
+    
+    let weekCount = 0;
+    let timeSlots = null;
+    
+    AndroidBridge.showToast("正在获取课程表数据,请稍候...");
+    
+    while (true) {
+        weekCount++;
+        
+        try {
+            const { html, hasCourses } = await fetchTimetable(requestDate);
+            
+            if (weekCount === 1) {
+                timeSlots = getTimeSlots(html);
+            }
+            
+            const weekCourses = parseTimetableFromHTML(html, requestDate);
+            
+            if (weekCourses.length > 0) {
+                allCourses.push(...weekCourses);
+                console.log(`第${weekCount}周: 找到 ${weekCourses.length} 门课程`);
+            }
+            
+            if (!hasCourses) {
+                console.log(`第${weekCount}周: 没有课程,停止获取`);
+                break;
+            }
+            
+            requestDate = addDays(requestDate, 7);
+            
+            if (weekCount >= 20) {
+                console.log("达到最大周数限制(20周),停止获取");
+                break;
+            }
+        } catch (error) {
+            console.error(`获取第${weekCount}周课程表失败:`, error);
+            AndroidBridge.showToast(`第${weekCount}周获取失败,继续下一周`);
+            requestDate = addDays(requestDate, 7);
+            continue;
+        }
+    }
+    
+    return {
+        courses: allCourses,
+        totalWeeks: weekCount,
+        timeSlots: timeSlots || getTimeSlots('')
+    };
+};
+
+// 主函数
+(async () => {
+    try {
+        AndroidBridge.showToast("正在启动GDIPU课程表导入...");
+        
+        const startDate = await promptForStartDate();
+        
+        const { courses, totalWeeks, timeSlots } = await fetchMultiWeekTimetable(startDate);
+        
+        if (courses.length === 0) {
+            AndroidBridge.showToast("未找到任何课程信息");
+            throw new Error("未找到课程信息");
+        }
+        
+        console.log(`总共找到 ${courses.length} 门课程,共 ${totalWeeks} 周`);
+        
+        const courseConfig = getSemesterConfig(startDate, totalWeeks);
+        
+        const success = await saveSchedule(courses, courseConfig, timeSlots);
+        
+        if (success) {
+            AndroidBridge.notifyTaskCompletion();
+        } else {
+            throw new Error("保存课程数据失败");
+        }
+        
+    } catch (error) {
+        console.error("导入课程表时出错:", error);
+        AndroidBridge.showToast("导入课程表失败:" + error.message);
+    }
+})();