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

add:哈尔滨工业大学适配(#559)

Haooz 2 недель назад
Родитель
Сommit
975cfb9a30
3 измененных файлов с 261 добавлено и 0 удалено
  1. 5 0
      index/root_index.yaml
  2. 8 0
      resources/HIT/adapters.yaml
  3. 248 0
      resources/HIT/hit.js

+ 5 - 0
index/root_index.yaml

@@ -847,4 +847,9 @@ schools:
     name: "河南科技大学"
     initial: "H"
     resource_folder: "HAUST"
+    
+  - id: "HIT"
+    name: "哈尔滨工业大学"
+    initial: "H"
+    resource_folder: "HIT"
     

+ 8 - 0
resources/HIT/adapters.yaml

@@ -0,0 +1,8 @@
+adapters:
+  - adapter_id: "HIT"
+    adapter_name: "哈尔滨工业大学研究生系统"
+    category: "POSTGRADUATE"
+    asset_js_path: "hit.js"
+    import_url: "http://yjsgl-hit-edu-cn.ivpn.hit.edu.cn:1080/xs/index"
+    maintainer: "Haooz"
+    description: "哈尔滨工业大学研究生教育管理系统,需通过VPN访问,登录后点击导入"

+ 248 - 0
resources/HIT/hit.js

@@ -0,0 +1,248 @@
+// 哈尔滨工业大学(hit.edu.cn) 研究生课表适配脚本
+// 研究生教育管理系统
+
+function parseWeeks(weekStr) {
+    if (!weekStr) return [];
+    const cleaned = weekStr.replace(/周/g, '').trim();
+    if (!cleaned) return [];
+    const weeks = [];
+    const parts = cleaned.split(/[,,]/);
+    for (const part of parts) {
+        const trimmed = part.trim();
+        if (!trimmed) continue;
+        const isOdd = trimmed.startsWith('单');
+        const isEven = trimmed.startsWith('双');
+        const rangeStr = isOdd || isEven ? trimmed.substring(1) : trimmed;
+        const rangeMatch = rangeStr.match(/^(\d+)\s*[-~]\s*(\d+)$/);
+        const singleMatch = rangeStr.match(/^(\d+)$/);
+        if (rangeMatch) {
+            const start = parseInt(rangeMatch[1], 10);
+            const end = parseInt(rangeMatch[2], 10);
+            for (let w = start; w <= end; w++) {
+                if (isOdd && w % 2 === 0) continue;
+                if (isEven && w % 2 !== 0) continue;
+                weeks.push(w);
+            }
+        } else if (singleMatch) {
+            const num = parseInt(singleMatch[1], 10);
+            if (isOdd && num % 2 === 0) continue;
+            if (isEven && num % 2 !== 0) continue;
+            weeks.push(num);
+        }
+    }
+    return [...new Set(weeks)].sort((a, b) => a - b);
+}
+
+function parseCourseCell(cellStr, day, sections) {
+    if (!cellStr || cellStr === 'null') return [];
+    const courses = [];
+    const entries = cellStr.split('<br/>');
+    for (const entry of entries) {
+        const trimmed = entry.trim();
+        if (!trimmed) continue;
+        // 格式: 课程名◇教师[周次]教室[节次]节
+        // 先找◇分割课程名和教师
+        const teacherSplit = trimmed.split('\u25C7');
+        if (teacherSplit.length < 2) {
+            console.log("HIT调试: 无◇分隔, text=" + trimmed.substring(0, 40));
+            continue;
+        }
+        const courseName = teacherSplit[0].trim();
+        const rest = teacherSplit.slice(1).join('\u25C7').trim();
+
+        // 从rest中提取: 教师[周次]教室[节次]节
+        // 找第一个[之前的是教师
+        const firstBracket = rest.indexOf('[');
+        if (firstBracket === -1) continue;
+        const teacher = rest.substring(0, firstBracket).trim();
+
+        // 提取周次: [周次] 中的内容
+        const weekMatch = rest.match(/\[(\d+[-~,,\d]+)周\]/);
+        if (!weekMatch) {
+            console.log("HIT调试: 无周次, rest=" + rest.substring(0, 50));
+            continue;
+        }
+        const weeksStr = weekMatch[1];
+        const weeks = parseWeeks(weeksStr);
+        if (weeks.length === 0) continue;
+
+        // 提取节次: 最后一个[节次]节 中的内容
+        const sectionMatch = rest.match(/\[(.+)\]节/);
+        if (!sectionMatch) {
+            console.log("HIT调试: 无节次, rest=" + rest.substring(0, 50));
+            continue;
+        }
+        const sectionStr = sectionMatch[1].trim();
+
+        // 提取教室: 周次]和节次[之间的内容
+        const afterWeek = rest.substring(rest.indexOf(']周]') + 3);
+        const beforeSection = afterWeek.substring(0, afterWeek.lastIndexOf('['));
+        const room = beforeSection.trim();
+
+        const sectionParts = sectionStr.split(/[,,\s]+/);
+        const startSection = parseInt(sectionParts[0], 10);
+        const endSection = sectionParts.length > 1 ? parseInt(sectionParts[sectionParts.length - 1], 10) : startSection;
+        if (isNaN(startSection) || isNaN(endSection)) {
+            console.log("HIT调试: 节次解析失败, sectionStr=" + sectionStr);
+            continue;
+        }
+
+        console.log("HIT调试: 解析成功, " + courseName + "|" + teacher + "|" + room + "|d" + day + "|s" + startSection + "-" + endSection + "|w" + weeks.length);
+        courses.push({
+            name: courseName,
+            teacher: teacher,
+            position: room,
+            day: day,
+            startSection: startSection,
+            endSection: endSection,
+            weeks: weeks
+        });
+    }
+    return courses;
+}
+
+function mergeCourses(courses) {
+    if (courses.length <= 1) return courses;
+    courses.sort((a, b) => {
+        return a.name.localeCompare(b.name) || a.teacher.localeCompare(b.teacher) ||
+               a.position.localeCompare(b.position) || (a.day || 0) - (b.day || 0) ||
+               (a.startSection || 0) - (b.startSection || 0);
+    });
+    const merged = [];
+    let cur = courses[0];
+    for (let i = 1; i < courses.length; i++) {
+        const nxt = courses[i];
+        if (cur.name === nxt.name && cur.teacher === nxt.teacher && cur.position === nxt.position &&
+            cur.day === nxt.day && cur.startSection === nxt.startSection && cur.endSection === nxt.endSection) {
+            cur.weeks = Array.from(new Set([...cur.weeks, ...nxt.weeks])).sort((a, b) => a - b);
+        } else {
+            merged.push(cur);
+            cur = nxt;
+        }
+    }
+    merged.push(cur);
+    return merged;
+}
+
+async function promptUserToStart() {
+    return await window.shiguangBridgePromise.showAlert(
+        "研究生课表导入",
+        "导入前请确保您已在浏览器中成功登录研究生系统",
+        "好的,开始导入"
+    );
+}
+
+async function fetchCurrentSchedule() {
+    const url = "/xs/index/getDqxqkb?sf_request_type=ajax";
+    try {
+        const response = await fetch(url, {
+            method: "POST",
+            headers: { "Content-Type": "application/x-www-form-urlencoded" },
+            credentials: "include"
+        });
+        if (!response.ok) return null;
+        const data = await response.json();
+        if (!data.isSuccess || !data.module) return null;
+        return data;
+    } catch (e) {
+        return null;
+    }
+}
+
+async function fetchSemesterInfo() {
+    const url = "/xs/index/getZcxx?sf_request_type=ajax";
+    try {
+        const response = await fetch(url, {
+            method: "POST",
+            headers: { "Content-Type": "application/x-www-form-urlencoded" },
+            credentials: "include"
+        });
+        if (!response.ok) return null;
+        const data = await response.json();
+        if (!data.isSuccess || !data.module) return null;
+        return data.module;
+    } catch (e) {
+        return null;
+    }
+}
+
+const dayMap = { mon: 1, tues: 2, wed: 3, thur: 4, fri: 5, sat: 6, sun: 7 };
+
+function parseScheduleData(scheduleData) {
+    const allCourses = [];
+    const module = scheduleData.module;
+    console.log("HIT调试: module行数=" + module.length);
+    for (const row of module) {
+        const sections = row.jcmc;
+        for (const [dayKey, dayNum] of Object.entries(dayMap)) {
+            const cellStr = row[dayKey];
+            if (!cellStr || cellStr === 'null') continue;
+            console.log("HIT调试: " + dayKey + "=" + cellStr.substring(0, 50));
+            const courses = parseCourseCell(cellStr, dayNum, sections);
+            console.log("HIT调试: 解析出" + courses.length + "门课");
+            allCourses.push(...courses);
+        }
+    }
+    return mergeCourses(allCourses);
+}
+
+async function saveCourses(courses) {
+    try {
+        await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        return true;
+    } catch (error) {
+        window.shiguangBridge.showToast("课程保存失败: " + error.message);
+        return false;
+    }
+}
+
+async function runImportFlow() {
+    const alertConfirmed = await promptUserToStart();
+    if (!alertConfirmed) {
+        window.shiguangBridge.showToast("用户取消了导入。");
+        return;
+    }
+
+    window.shiguangBridge.showToast("正在获取课表数据...");
+
+    const [scheduleData, semesterInfo] = await Promise.all([
+        fetchCurrentSchedule(),
+        fetchSemesterInfo()
+    ]);
+
+    if (!scheduleData) {
+        window.shiguangBridge.showToast("未能获取课表数据,请检查网络环境或登录状态。");
+        return;
+    }
+
+    console.log("HIT调试: API返回成功, module长度=" + scheduleData.module.length);
+    console.log("HIT调试: 第一行数据=" + JSON.stringify(scheduleData.module[0]));
+
+    const courses = parseScheduleData(scheduleData);
+    console.log("HIT调试: 总共解析=" + courses.length + "门课");
+    if (courses.length > 0) {
+        console.log("HIT调试: 第一门=" + JSON.stringify(courses[0]));
+    }
+    if (courses.length === 0) {
+        window.shiguangBridge.showToast("未找到课程数据");
+        return;
+    }
+
+    const saveResult = await saveCourses(courses);
+    if (!saveResult) return;
+
+    if (semesterInfo && semesterInfo.ZCJSSJ) {
+        try {
+            await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({
+                semesterStartDate: null,
+                semesterTotalWeeks: 20
+            }));
+        } catch (e) {}
+    }
+
+    const semesterName = semesterInfo ? semesterInfo.MC : "当前学期";
+    window.shiguangBridge.showToast("课程导入成功!" + semesterName + ",共 " + courses.length + " 门课程");
+    window.shiguangBridge.notifyTaskCompletion();
+}
+
+runImportFlow();