Browse Source

Add HUAT (#1)

* Fix resource_folder indentation in root_index.yaml

* Add files via upload
John Smith 2 tuần trước cách đây
mục cha
commit
766aaa0a5d
4 tập tin đã thay đổi với 610 bổ sung1 xóa
  1. 6 1
      index/root_index.yaml
  2. 595 0
      resources/HUAT/HUAT.js
  3. 0 0
      resources/HUAT/Read.md
  4. 9 0
      resources/HUAT/adapters.yaml

+ 6 - 1
index/root_index.yaml

@@ -206,4 +206,9 @@ schools:
   - id: "AHSZU"
     name: "宿州学院"
     initial: "S"
-    resource_folder: "AHSZU"    
+    resource_folder: "AHSZU"    
+
+    - id: "HUAT"
+    name: "湖北汽车工业学院"
+    initial: "H"
+    resource_folder: "HUAT"    

+ 595 - 0
resources/HUAT/HUAT.js

@@ -0,0 +1,595 @@
+// 文件: school.js - 修复节次错误
+// ==================== 验证函数 ====================
+function validateDate(dateStr) {
+    if (!dateStr || dateStr.trim().length === 0) {
+        return "日期不能为空!";
+    }
+    const datePattern = /^\d{4}-\d{2}-\d{2}$/;
+    if (!datePattern.test(dateStr)) {
+        return "日期格式必须是 YYYY-MM-DD!";
+    }
+    const parts = dateStr.split('-');
+    const year = parseInt(parts[0]);
+    const month = parseInt(parts[1]);
+    const day = parseInt(parts[2]);
+    const date = new Date(year, month - 1, day);
+    if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
+        return "请输入有效的日期!";
+    }
+    return false;
+}
+function validateName(name) {
+    if (name === null || name.trim().length === 0) {
+        return "输入不能为空!";
+    }
+    if (name.length < 2) {
+        return "至少需要2个字符!";
+    }
+    return false;
+}
+// ==================== 课表数据提取函数 ====================
+/**
+ * 从页面提取课表数据 - 修复节次错误
+ */
+function extractCourseData() {
+    console.log("开始提取湖北汽车工业学院课表数据...");
+    
+    const courses = [];
+    
+    // 获取所有课表行 - 使用更精确的选择器
+    const rows = document.querySelectorAll('.el-table__body-wrapper tbody tr');
+    console.log(`找到 ${rows.length} 行课表数据`);
+    
+    // 获取时间标签,用于验证行对应的节次
+    const timeLabels = document.querySelectorAll('.el-table__header-wrapper th .cell, .el-table__header-wrapper th span');
+    console.log("时间标签:", Array.from(timeLabels).map(el => el.textContent));
+    
+    rows.forEach((row, rowIndex) => {
+        // 获取该行的所有单元格
+        const cells = row.querySelectorAll('td');
+        
+        // 从第1个单元格开始是周一至周日(跳过第0个时间单元格)
+        for (let dayIndex = 1; dayIndex < cells.length; dayIndex++) {
+            const cell = cells[dayIndex];
+            
+            // 星期映射修正:第1列=周一(0), 第2列=周二(1), ... 第7列=周日(6)
+            const day = dayIndex - 1;
+            
+            // 查找单元格内的所有课程块 - 使用更通用的选择器
+            const courseBlocks = cell.querySelectorAll('[class*="theory"], .theory, [class*="course"], div[style*="background"]');
+            
+            if (courseBlocks.length > 0) {
+                courseBlocks.forEach(block => {
+                    try {
+                        const course = parseCourseBlock(block, day, rowIndex);
+                        if (course) {
+                            courses.push(course);
+                        }
+                    } catch (e) {
+                        console.error("解析课程块失败:", e);
+                    }
+                });
+            }
+        }
+    });
+    
+    // 如果没找到课程,尝试另一种选择器
+    if (courses.length === 0) {
+        console.log("尝试使用备用选择器...");
+        const allCourseElements = document.querySelectorAll('[class*="theory"], .theory, [class*="course"]');
+        console.log(`找到 ${allCourseElements.length} 个可能的课程元素`);
+        
+        allCourseElements.forEach(element => {
+            // 尝试找到元素所在的单元格和行
+            const td = element.closest('td');
+            if (td) {
+                const tr = td.closest('tr');
+                if (tr) {
+                    const rowIndex = Array.from(tr.parentNode.children).indexOf(tr);
+                    const dayIndex = Array.from(td.parentNode.children).indexOf(td);
+                    
+                    if (dayIndex >= 1 && dayIndex <= 7) {
+                        // 星期映射修正:第1列=周一(0), 第2列=周二(1), ... 第7列=周日(6)
+                        const day = dayIndex - 1;
+                        const course = parseCourseBlock(element, day, rowIndex);
+                        if (course) {
+                            courses.push(course);
+                        }
+                    }
+                }
+            }
+        });
+    }
+    
+    // 去重
+    const uniqueCourses = removeDuplicates(courses);
+    
+    // 按星期和节次排序
+    uniqueCourses.sort((a, b) => {
+        if (a.day !== b.day) return a.day - b.day;
+        return a.startSection - b.startSection;
+    });
+    
+    console.log(`共提取到 ${uniqueCourses.length} 门课程`);
+    uniqueCourses.forEach(c => {
+        console.log(`星期${c.day+1}: ${c.name} - ${c.teacher} - ${c.position} - 第${c.startSection}-${c.endSection}节`);
+    });
+    
+    return uniqueCourses;
+}
+/**
+ * 解析课程块 - 修复节次映射
+ */
+function parseCourseBlock(block, day, rowIndex) {
+    // 获取所有子元素
+    const children = block.children;
+    let name = '', teacher = '', position = '', weeks = [];
+    
+    // 方法1:通过子元素解析
+    if (children.length >= 3) {
+        // 课程名称通常在h3标签中
+        const nameElement = block.querySelector('h3');
+        if (nameElement) {
+            name = nameElement.innerText.trim();
+            // 只移除括号内是纯数字且不是课程名称标识的情况
+            // 但保留(24)这种课程代码
+            // 如果括号内是纯数字且长度大于2,可能是周次信息,否则保留
+            name = name.replace(/[((]\d+[))]/g, function(match) {
+                // 提取括号内的数字
+                const num = match.replace(/[(())]/g, '');
+                // 如果数字大于30,可能是周次信息,移除;否则保留(如24是课程代码)
+                if (parseInt(num) > 30) {
+                    return '';
+                }
+                return match;
+            }).trim();
+        }
+        
+        // 教师信息通常在第一个p标签中
+        const teacherElement = block.querySelector('p:first-child span:first-child, p:nth-child(1) span');
+        if (teacherElement) {
+            teacher = teacherElement.innerText.trim();
+            // 移除课时信息(如"2H")
+            teacher = teacher.replace(/\d+H?$/, '').trim();
+        }
+        
+        // 周次和教室信息通常在第二个p标签中
+        const weekPositionElement = block.querySelector('p:nth-child(2) span, p:last-child span');
+        if (weekPositionElement) {
+            const text = weekPositionElement.innerText.trim();
+            const result = parseWeekAndPosition(text);
+            weeks = result.weeks;
+            position = result.position;
+        }
+    }
+    
+    // 方法2:如果子元素解析失败,通过文本行解析
+    if (!name || !teacher || !position) {
+        const text = block.innerText;
+        const lines = text.split('\n').filter(line => line.trim());
+        
+        if (lines.length >= 3) {
+            // 第1行:课程名称
+            if (!name) {
+                name = lines[0].trim();
+                // 只移除括号内是纯数字且不是课程名称标识的情况
+                name = name.replace(/[((]\d+[))]/g, function(match) {
+                    const num = match.replace(/[(())]/g, '');
+                    if (parseInt(num) > 30) {
+                        return '';
+                    }
+                    return match;
+                }).trim();
+            }
+            
+            // 第2行:教师信息
+            if (!teacher && lines[1]) {
+                teacher = lines[1].trim();
+                teacher = teacher.replace(/\d+H?$/, '').trim();
+            }
+            
+            // 第3行:周次和教室
+            if (!position && lines[2]) {
+                const result = parseWeekAndPosition(lines[2].trim());
+                weeks = result.weeks;
+                position = result.position;
+            }
+        }
+    }
+    
+    // 如果还是没有找到教室,尝试从整个块中提取数字教室
+    if (!position || position === '未知教室') {
+        position = extractClassroom(block.innerText);
+    }
+    
+    // 节次映射彻底修正:按行索引重新定义startSection和endSection
+    let startSection, endSection;
+    switch(rowIndex) {
+        case 0: // 第1行:第1节
+            startSection = 1;
+            endSection = 1;
+            break;
+        case 1: // 第2行:第2节
+            startSection = 2;
+            endSection = 2;
+            break;
+        case 2: // 第3行:第3节
+            startSection = 3;
+            endSection = 3;
+            break;
+        case 3: // 第4行:第4节
+            startSection = 4;
+            endSection = 4;
+            break;
+        case 4: // 第5行:第5节
+            startSection = 5;
+            endSection = 5;
+            break;
+        case 5: // 第6行:第6节
+            startSection = 6;
+            endSection = 6;
+            break;
+        case 6: // 第7行:第7节
+            startSection = 7;
+            endSection = 7;
+            break;
+        case 7: // 第8行:第8节
+            startSection = 8;
+            endSection = 8;
+            break;
+        case 8: // 第9行:第9节
+            startSection = 9;
+            endSection = 9;
+            break;
+        case 9: // 第10行:第10节
+            startSection = 10;
+            endSection = 10;
+            break;
+        case 10: // 第11行:第11节
+            startSection = 11;
+            endSection = 11;
+            break;
+        default: // 默认第1节
+            startSection = 1;
+            endSection = 1;
+    }
+    
+    // 检查是否有rowspan(连堂课程)- 修正连堂节次计算逻辑
+    const td = block.closest('td');
+    if (td) {
+        const rowspan = td.getAttribute('rowspan');
+        if (rowspan) {
+            const span = parseInt(rowspan);
+            if (span > 1) {
+                // 连堂时,结束节次 = 开始节次 + 跨行数 - 1
+                endSection = startSection + span - 1;
+                // 限制节次最大值不超过11
+                if (endSection > 11) endSection = 11;
+            }
+        }
+    }
+    
+    // 只有提取到有效的课程名称才返回
+    if (!name || name.includes('节') || name.length < 2 || name.includes('理论课')) {
+        return null;
+    }
+    
+    // 特殊处理体育课(可能没有教室)
+    if (name.includes('体育') && position === '未知教室') {
+        position = '操场';
+    }
+    
+    const course = {
+        name: name,
+        teacher: teacher || '未知教师',
+        position: position || '未知教室',
+        day: day,
+        startSection: startSection,
+        endSection: endSection,
+        weeks: weeks.length > 0 ? weeks : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
+        isCustomTime: false
+    };
+    
+    return course;
+}
+/**
+ * 解析周次和教室
+ */
+function parseWeekAndPosition(text) {
+    let weeks = [];
+    let position = '未知教室';
+    
+    if (!text) return { weeks, position };
+    
+    console.log("解析周次和教室:", text);
+    
+    // 匹配周次模式:如 "3-16周"、"1-12周"、"6-8周"
+    const weekPattern = /(\d+-\d+周|\d+,\d+周|\d+周)/;
+    const weekMatch = text.match(weekPattern);
+    
+    if (weekMatch) {
+        const weekStr = weekMatch[1];
+        weeks = parseWeeks(weekStr);
+        
+        // 剩余部分可能是教室
+        let remaining = text.replace(weekStr, '').trim();
+        
+        // 如果剩余部分包含数字,很可能是教室
+        if (remaining && /\d+/.test(remaining)) {
+            position = remaining;
+        } else {
+            // 尝试从原文本中提取教室(通常是4位数字)
+            const roomMatch = text.match(/\b\d{3,4}\b/);
+            if (roomMatch) {
+                position = roomMatch[0];
+            }
+        }
+    } else {
+        // 如果没有周次信息,直接尝试提取教室
+        const roomMatch = text.match(/\b\d{3,4}\b/);
+        if (roomMatch) {
+            position = roomMatch[0];
+            weeks = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
+        }
+    }
+    
+    // 清理教室字符串
+    if (position && position !== '未知教室') {
+        // 只保留数字
+        position = position.replace(/[^\d]/g, '');
+    }
+    
+    return { weeks, position };
+}
+/**
+ * 从文本中提取教室
+ */
+function extractClassroom(text) {
+    // 匹配常见的教室格式:1205、6604、1231、2303、6403、6212、2103、1233、6104、1203
+    const roomPatterns = [
+        /\b\d{4}\b/,           // 4位数字
+        /\b\d{3}\b/,           // 3位数字
+        /[0-9]{3,4}/           // 任意3-4位数字
+    ];
+    
+    for (let pattern of roomPatterns) {
+        const match = text.match(pattern);
+        if (match) {
+            return match[0];
+        }
+    }
+    
+    return '未知教室';
+}
+/**
+ * 解析周次字符串
+ */
+function parseWeeks(weekStr) {
+    const weeks = [];
+    if (!weekStr) return [];
+    
+    // 移除"周"字
+    weekStr = weekStr.replace(/周/g, '').trim();
+    
+    try {
+        if (weekStr.includes(',')) {
+            weekStr.split(',').forEach(part => {
+                if (part.includes('-')) {
+                    const [start, end] = part.split('-').map(Number);
+                    for (let i = start; i <= end; i++) weeks.push(i);
+                } else {
+                    const w = parseInt(part);
+                    if (!isNaN(w)) weeks.push(w);
+                }
+            });
+        } else if (weekStr.includes('-')) {
+            const [start, end] = weekStr.split('-').map(Number);
+            for (let i = start; i <= end; i++) weeks.push(i);
+        } else {
+            const w = parseInt(weekStr);
+            if (!isNaN(w)) weeks.push(w);
+        }
+    } catch (e) {
+        console.error("解析周次失败:", weekStr, e);
+    }
+    
+    return weeks.length > 0 ? weeks.sort((a,b) => a-b) : [];
+}
+/**
+ * 去重
+ */
+function removeDuplicates(courses) {
+    const seen = new Map();
+    return courses.filter(course => {
+        // 创建唯一键:课程名+教师+星期+开始节次
+        const key = `${course.name}-${course.teacher}-${course.day}-${course.startSection}`;
+        if (seen.has(key)) {
+            return false;
+        }
+        seen.set(key, true);
+        return true;
+    });
+}
+/**
+ * 提取学期信息
+ */
+function extractSemesterInfo() {
+    return {
+        semesterStartDate: "2026-03-01",
+        semesterTotalWeeks: 20
+    };
+}
+// ==================== 弹窗和导入函数 ====================
+async function demoAlert() {
+    try {
+        const confirmed = await window.AndroidBridgePromise.showAlert(
+            "📚 湖北汽车工业学院课表导入",
+            "将提取当前页面的课表数据并导入到App\n\n" +
+            "📌 请确认已在课表页面\n" +
+            "📌 将提取所有可见课程",
+            "开始导入",
+            "取消"
+        );
+        return confirmed;
+    } catch (error) {
+        console.error("显示弹窗错误:", error);
+        return false;
+    }
+}
+async function demoPrompt() {
+    try {
+        const semesterInfo = extractSemesterInfo();
+        const semesterStart = await window.AndroidBridgePromise.showPrompt(
+            "📅 设置开学日期",
+            "请输入本学期开学日期",
+            semesterInfo.semesterStartDate,
+            "validateDate"
+        );
+        return semesterStart || semesterInfo.semesterStartDate;
+    } catch (error) {
+        console.error("日期输入错误:", error);
+        return "2026-03-01";
+    }
+}
+/**
+ * 导入预设时间段 - 新增函数
+ * 用于导入测试用的11个时间段(每个时间段1分钟)
+ */
+async function importPresetTimeSlots() {
+    console.log("正在准备预设时间段数据...");
+    const presetTimeSlots = [
+        { "number": 1, "startTime": "08:10", "endTime": "08:55" },
+        { "number": 2, "startTime": "09:00", "endTime": "09:45" },
+        { "number": 3, "startTime": "10:05", "endTime": "10:50" },
+        { "number": 4, "startTime": "10:55", "endTime": "11:40" },
+        { "number": 5, "startTime": "14:30", "endTime": "15:15" },
+        { "number": 6, "startTime": "15:20", "endTime": "16:05" },
+        { "number": 7, "startTime": "16:25", "endTime": "17:10" },
+        { "number": 8, "startTime": "17:15", "endTime": "18:00" },
+        { "number": 9, "startTime": "18:45", "endTime": "19:30" },
+        { "number": 10, "startTime": "19:35", "endTime": "20:20" },
+        { "number": 11, "startTime": "20:25", "endTime": "21:10" }
+    ];
+    try {
+        console.log("正在尝试导入预设时间段...");
+        const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
+        if (result === true) {
+            console.log("预设时间段导入成功!");
+            window.AndroidBridge.showToast("测试时间段导入成功!");
+            return true;
+        } else {
+            console.log("预设时间段导入未成功,结果:" + result);
+            window.AndroidBridge.showToast("测试时间段导入失败,请查看日志。");
+            return false;
+        }
+    } catch (error) {
+        console.error("导入时间段时发生错误:", error);
+        window.AndroidBridge.showToast("导入时间段失败: " + error.message);
+        return false;
+    }
+}
+async function importSchedule() {
+    try {
+        AndroidBridge.showToast("正在提取课表数据...");
+        
+        const courses = extractCourseData();
+        
+        if (courses.length === 0) {
+            await window.AndroidBridgePromise.showAlert(
+                "⚠️ 提取失败",
+                "未找到课表数据,请确认已在课表页面",
+                "知道了"
+            );
+            return false;
+        }
+        
+        // 预览
+        const preview = await window.AndroidBridgePromise.showAlert(
+            "📊 数据预览",
+            `共找到 ${courses.length} 门课程\n\n` +
+            `示例:\n${courses.slice(0, 5).map(c => 
+                `• 周${c.day+1} ${c.name} - 第${c.startSection}-${c.endSection}节`
+            ).join('\n')}`,
+            "确认导入",
+            "取消"
+        );
+        
+        if (!preview) {
+            return false;
+        }
+        
+        // 导入课程
+        AndroidBridge.showToast("正在导入课程...");
+        const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        
+        if (result === true) {
+            const semesterDate = await demoPrompt();
+            const configResult = await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
+                semesterStartDate: semesterDate,
+                semesterTotalWeeks: 20,
+                defaultClassDuration: 45,
+                defaultBreakDuration: 10,
+                firstDayOfWeek: 1
+            }));
+            
+            if (configResult === true) {
+                AndroidBridge.showToast(`✅ 导入成功!共${courses.length}门课程`);
+                return true;
+            }
+        }
+        
+        AndroidBridge.showToast("❌ 导入失败");
+        return false;
+        
+    } catch (error) {
+        console.error("导入错误:", error);
+        AndroidBridge.showToast("导入出错: " + error.message);
+        return false;
+    }
+}
+async function runAllDemosSequentially() {
+    AndroidBridge.showToast("🚀 课表导入助手启动...");
+    
+    // 检查页面
+    if (!window.location.href.includes('studentHome/expectCourseTable')) {
+        const goToPage = await window.AndroidBridgePromise.showAlert(
+            "页面提示",
+            "当前不在课表页面,是否跳转?",
+            "跳转",
+            "取消"
+        );
+        if (goToPage) {
+            window.location.href = 'http://neweas.huat.edu.cn/#/studentHome/expectCourseTable';
+        }
+        return;
+    }
+    
+    const start = await demoAlert();
+    if (!start) {
+        AndroidBridge.showToast("已取消");
+        return;
+    }
+    
+    // 可以选择是否导入时间段
+    const importTimeSlots = await window.AndroidBridgePromise.showAlert(
+        "⏰ 导入时间段",
+        "是否要导入预设的时间段数据?\n",
+        "导入",
+        "跳过"
+    );
+    
+    if (importTimeSlots) {
+        await importPresetTimeSlots();
+    }
+    
+    await importSchedule();
+    
+    AndroidBridge.notifyTaskCompletion();
+}
+// 导出函数
+window.validateDate = validateDate;
+window.validateName = validateName;
+window.extractCourseData = extractCourseData;
+window.importPresetTimeSlots = importPresetTimeSlots;
+// 启动
+runAllDemosSequentially();

+ 0 - 0
resources/HUAT/Read.md


+ 9 - 0
resources/HUAT/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/HUAT/adapters.yaml
+adapters:
+  - adapter_id: "HUAT"
+    adapter_name: "湖北汽车工业学院"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "HUAT.js"
+    import_url: "https://cas.huat.edu.cn/authserver/login?service=http%3A%2F%2Fneweas.huat.edu.cn%2Flogin"
+    maintainer: "老考滤一下"
+    description: "湖北汽车工业学院适配教务,非本校开发者适配如果有误建议提交issues"