Răsfoiți Sursa

Merge pull request #540 from XingHeYuZhuan/pending

星河欲转 3 săptămâni în urmă
părinte
comite
99c5e1c3c7
3 a modificat fișierele cu 507 adăugiri și 0 ștergeri
  1. 5 0
      index/root_index.yaml
  2. 17 0
      resources/AUFE/adapters.yaml
  3. 485 0
      resources/AUFE/aufe_01.js

+ 5 - 0
index/root_index.yaml

@@ -792,3 +792,8 @@ schools:
     name: "重庆工程学院"
     name: "重庆工程学院"
     initial: "C"
     initial: "C"
     resource_folder: "CQIE"
     resource_folder: "CQIE"
+
+  - id: "AUFE"
+    name: "安徽财经大学"
+    initial: "A"
+    resource_folder: "AUFE"    

+ 17 - 0
resources/AUFE/adapters.yaml

@@ -0,0 +1,17 @@
+# resources/AUFE/adapters.yaml
+adapters:
+  - adapter_id: "AUFE_01"
+    adapter_name: "安徽财经大学校内入口"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "aufe_01.js"
+    import_url: "http://all.aufe.edu.cn"
+    maintainer: "星河欲转"
+    description: "安徽财经大学urp教务适配,进入教务系统后点击开始导入,非本校开发者适配如果有误建议提交issues"
+
+  - adapter_id: "AUFE_02"
+    adapter_name: "安徽财经大学Webvpn入口"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "aufe_01.js"
+    import_url: "http://vpn.aufe.edu.cn"
+    maintainer: "星河欲转"
+    description: "安徽财经大学urp教务适配,进入教务系统后点击开始导入,非本校开发者适配如果有误建议提交issues"  

+ 485 - 0
resources/AUFE/aufe_01.js

@@ -0,0 +1,485 @@
+// 安徽财经大学 (aufe.edu.cn) 拾光课程表适配脚本
+// 非该大学开发者适配,开发者无法及时发现问题
+// 出现问题请联系开发者或者提交PR更改,这更加快速
+
+// 配置
+
+// 可手动指定域名,留空则自动从当前页面获取
+const BASE_URL = ""; // 例如 "http://jwcxk2-aufe-edu-cn.vpn2.aufe.edu.cn:8118"
+
+// 工具函数
+
+// 自动获取当前域名
+function getBaseUrl() {
+    if (BASE_URL) return BASE_URL;
+    const url = new URL(window.location.href);
+    return url.origin;
+}
+
+// 解析 classWeek 字符串 (支持不定长度)
+function parseWeekString(weekStr) {
+    let weeks = [];
+    if (!weekStr) return weeks;
+    for (let i = 0; i < weekStr.length; i++) {
+        if (weekStr[i] === '1') weeks.push(i + 1);
+    }
+    return weeks;
+}
+
+// 格式化时间 (0800 -> 08:00)
+function formatTime(timeStr) {
+    if (timeStr && timeStr.length === 4) {
+        return timeStr.substring(0, 2) + ":" + timeStr.substring(2);
+    }
+    return timeStr;
+}
+
+// 格式化日期 (20260831 -> 2026-08-31)
+function formatDate(dateStr) {
+    if (dateStr && dateStr.length === 8) {
+        return dateStr.substring(0, 4) + "-" + dateStr.substring(4, 6) + "-" + dateStr.substring(6, 8);
+    }
+    return dateStr;
+}
+
+/**
+ * 节次与周次合并去重函数(供开发者参考)
+ * @param {Array<Object>} courses 原始解析课程数组
+ * @returns {Array<Object>} 合并去重后的课程数组
+ */
+function mergeAndDistinctCourses(courses) {
+    if (!Array.isArray(courses) || courses.length <= 1) return courses;
+
+    // 1. 深拷贝并规范周次数据,过滤无效项
+    const list = courses.map(c => ({
+        ...c,
+        name: c.name || '',
+        teacher: c.teacher || '',
+        position: c.position || '',
+        weeks: Array.isArray(c.weeks) ? [...c.weeks].sort((a, b) => a - b) : []
+    }));
+
+    // 阶段 1:合并连续节次与完全重复记录(前提:名称、教师、地点、星期、周次一致)
+    list.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.weeks.join(',').localeCompare(b.weeks.join(',')) ||
+               (a.startSection || 0) - (b.startSection || 0);
+    });
+
+    const step1Merged = [];
+    let current = list[0];
+
+    for (let i = 1; i < list.length; i++) {
+        const next = list[i];
+
+        const isSameCourseAndWeeks =
+            current.name === next.name &&
+            current.teacher === next.teacher &&
+            current.position === next.position &&
+            current.day === next.day &&
+            current.weeks.join(',') === next.weeks.join(',');
+
+        const isContinuous = current.endSection + 1 === next.startSection;
+        const isDuplicate = current.startSection === next.startSection && current.endSection === next.endSection;
+
+        if (isSameCourseAndWeeks && isContinuous) {
+            // 节次连续:延长结束节次 (如 1-2 节 + 3-4 节 -> 1-4 节)
+            current.endSection = next.endSection;
+        } else if (isSameCourseAndWeeks && isDuplicate) {
+            // 完全重复:跳过
+            continue;
+        } else {
+            step1Merged.push(current);
+            current = next;
+        }
+    }
+    step1Merged.push(current);
+
+    // 阶段 2:合并同节次的周次(前提:名称、教师、地点、星期、开始/结束节次一致)
+    step1Merged.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) ||
+               (a.endSection || 0) - (b.endSection || 0);
+    });
+
+    const step2Merged = [];
+    let cur = step1Merged[0];
+
+    for (let i = 1; i < step1Merged.length; i++) {
+        const nxt = step1Merged[i];
+
+        const isSameCourseAndSection =
+            cur.name === nxt.name &&
+            cur.teacher === nxt.teacher &&
+            cur.position === nxt.position &&
+            cur.day === nxt.day &&
+            cur.startSection === nxt.startSection &&
+            cur.endSection === nxt.endSection;
+
+        if (isSameCourseAndSection) {
+            // 周次合并去重 (如 1-8 周 + 9-16 周 -> 1-16 周)
+            cur.weeks = Array.from(new Set([...cur.weeks, ...nxt.weeks])).sort((a, b) => a - b);
+        } else {
+            step2Merged.push(cur);
+            cur = nxt;
+        }
+    }
+    step2Merged.push(cur);
+
+    return step2Merged;
+}
+
+// 从HTML中提取学期列表和动态接口路径
+function parseIndexHtml(html, baseUrl) {
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(html, "text/html");
+    
+    const select = doc.getElementById("planCode");
+    if (!select) return null;
+    
+    const options = select.querySelectorAll("option");
+    const semesterList = [];
+    let defaultIndex = 0;
+    
+    options.forEach((opt, index) => {
+        const value = opt.getAttribute("value");
+        const text = opt.textContent.trim();
+        if (value && value !== "no") {
+            semesterList.push({
+                value: value,
+                label: text,
+                isCurrent: text.includes("当前")
+            });
+            if (text.includes("当前")) defaultIndex = semesterList.length - 1;
+        }
+    });
+    
+    // 从JS中提取动态接口路径
+    const scripts = doc.querySelectorAll("script");
+    let ajaxUrl = null;
+    for (const script of scripts) {
+        const content = script.textContent;
+        if (content && content.includes("ajaxStudentSchedule")) {
+            const match = content.match(/url\s*:\s*"([^"]+ajaxStudentSchedule[^"]+)"/);
+            if (match) {
+                ajaxUrl = match[1];
+                break;
+            }
+            const match2 = content.match(/\/student\/courseSelect\/thisSemesterCurriculum\/[A-Za-z0-9]+\/ajaxStudentSchedule\/past\/callback/);
+            if (match2) {
+                ajaxUrl = match2[0];
+                break;
+            }
+        }
+    }
+    
+    if (!ajaxUrl) {
+        const forms = doc.querySelectorAll("form");
+        for (const form of forms) {
+            const action = form.getAttribute("action");
+            if (action && action.includes("ajaxStudentSchedule")) {
+                ajaxUrl = action;
+                break;
+            }
+        }
+    }
+    
+    return { semesterList, defaultIndex, ajaxUrl };
+}
+
+// 从校历页面解析开学日期
+function parseStartDate(html) {
+    // 匹配 var rq = "20260831";
+    const match = html.match(/var\s+rq\s*=\s*"(\d{8})"/);
+    if (match) {
+        return formatDate(match[1]);
+    }
+    return null;
+}
+
+// 用户交互函数
+
+async function promptUserToStart() {
+    return await window.shiguangBridgePromise.showAlert(
+        "教务系统课表导入",
+        "导入前请确保您已在浏览器中成功登录教务系统",
+        "好的,开始导入"
+    );
+}
+
+async function selectSemesterFromList(semesterList, defaultIndex) {
+    const labels = semesterList.map(s => s.label);
+    const index = await window.shiguangBridgePromise.showSingleSelection(
+        "选择学期",
+        JSON.stringify(labels),
+        defaultIndex
+    );
+    if (index === null) return null;
+    return semesterList[index];
+}
+
+async function getStartDate(defaultDate) {
+    const result = await window.shiguangBridgePromise.showPrompt(
+        "设置开学日期",
+        "请输入本学期开学日期(格式:YYYY-MM-DD):",
+        defaultDate || "",
+        "validateDate"
+    );
+    return result;
+}
+
+// 验证函数
+
+function validateDate(input) {
+    if (!input || input.trim().length === 0) return "开学日期不能为空!";
+    const regex = /^\d{4}-\d{2}-\d{2}$/;
+    if (!regex.test(input)) return "请输入正确格式(例如:2026-08-31)";
+    const parts = input.split("-");
+    const year = parseInt(parts[0]);
+    const month = parseInt(parts[1]);
+    const day = parseInt(parts[2]);
+    if (month < 1 || month > 12) return "月份必须在1-12之间";
+    if (day < 1 || day > 31) return "日期必须在1-31之间";
+    return false;
+}
+
+// 数据获取与解析
+
+async function fetchIndexPage(baseUrl) {
+    try {
+        const response = await fetch(`${baseUrl}/student/courseSelect/calendarSemesterCurriculum/index`, {
+            method: "GET",
+            credentials: "include"
+        });
+        const html = await response.text();
+        const result = parseIndexHtml(html, baseUrl);
+        if (!result || !result.semesterList || result.semesterList.length === 0) {
+            throw new Error("未找到学期列表");
+        }
+        if (!result.ajaxUrl) {
+            throw new Error("未找到课表接口路径");
+        }
+        return result;
+    } catch (e) {
+        window.shiguangBridge.showToast("获取首页失败: " + e.message);
+        return null;
+    }
+}
+
+async function fetchStartDate(baseUrl) {
+    try {
+        const response = await fetch(`${baseUrl}/indexCalendar`, {
+            method: "GET",
+            credentials: "include"
+        });
+        const html = await response.text();
+        return parseStartDate(html);
+    } catch (e) {
+        console.error("获取开学日期失败:", e);
+        return null;
+    }
+}
+
+async function fetchTimeSlots(baseUrl) {
+    try {
+        const response = await fetch(`${baseUrl}/ajax/getSectionAndTime`, {
+            headers: {
+                "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
+                "x-requested-with": "XMLHttpRequest"
+            },
+            body: "planNumber=&ff=f",
+            method: "POST",
+            credentials: "include"
+        });
+
+        const data = await response.json();
+        if (!data || !data.sectionTime || !Array.isArray(data.sectionTime)) {
+            return null;
+        }
+
+        const timeSlots = data.sectionTime.map(item => ({
+            number: parseInt(item.sessionName) || item.djjc,
+            startTime: formatTime(item.startTime),
+            endTime: formatTime(item.endTime)
+        }));
+
+        // 提取总周数
+        let totalWeeks = null;
+        if (data.section && data.section.zs) {
+            totalWeeks = parseInt(data.section.zs);
+        }
+
+        return { timeSlots, totalWeeks };
+    } catch (e) {
+        console.error("获取时间段失败:", e);
+        return null;
+    }
+}
+
+async function fetchCourses(baseUrl, ajaxUrl, planCode) {
+    try {
+        window.shiguangBridge.showToast("正在获取教务数据...");
+
+        const fullUrl = `${baseUrl}${ajaxUrl}`;
+
+        const response = await fetch(fullUrl, {
+            headers: {
+                "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
+                "x-requested-with": "XMLHttpRequest"
+            },
+            body: `&planCode=${planCode}`,
+            method: "POST",
+            credentials: "include"
+        });
+
+        const data = await response.json();
+
+        if (!data) throw new Error("服务器未返回任何数据");
+        if (!data.dateList || !Array.isArray(data.dateList)) {
+            console.error("教务返回数据异常:", data);
+            throw new Error("未能获取到课程列表,请检查是否已登录或该学期是否有课");
+        }
+
+        let courses = [];
+        data.dateList.forEach(plan => {
+            if (plan && plan.selectCourseList && Array.isArray(plan.selectCourseList)) {
+                plan.selectCourseList.forEach(c => {
+                    const teacher = (c.attendClassTeacher || "").replace(/\* /g, "").trim();
+
+                    if (c.timeAndPlaceList && Array.isArray(c.timeAndPlaceList)) {
+                        c.timeAndPlaceList.forEach(tp => {
+                            let position = "";
+                            if (tp.campusName) position += tp.campusName;
+                            if (tp.teachingBuildingName) position += tp.teachingBuildingName;
+                            if (tp.classroomName) position += tp.classroomName;
+
+                            courses.push({
+                                name: c.courseName || c.coureName || "未知课程",
+                                teacher: teacher,
+                                position: position || "未知地点",
+                                day: tp.classDay,
+                                startSection: tp.classSessions,
+                                endSection: tp.classSessions + tp.continuingSession - 1,
+                                weeks: parseWeekString(tp.classWeek),
+                                isCustomTime: false
+                            });
+                        });
+                    }
+                });
+            }
+        });
+
+        if (courses.length === 0) {
+            throw new Error("该学期暂无排课数据");
+        }
+
+        return courses;
+    } catch (e) {
+        window.shiguangBridge.showToast("获取课程失败: " + e.message);
+        return null;
+    }
+}
+
+// 数据保存
+
+async function saveToApp(courses, timeSlots, startDate, totalWeeks) {
+    try {
+        // 合并去重
+        const mergedCourses = mergeAndDistinctCourses(courses);
+
+        const courseSuccess = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(mergedCourses));
+        if (!courseSuccess) {
+            throw new Error("课程保存失败");
+        }
+
+        if (timeSlots && timeSlots.length > 0) {
+            await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+        }
+
+        const config = {};
+        if (totalWeeks) {
+            config.semesterTotalWeeks = totalWeeks;
+        } else {
+            config.semesterTotalWeeks = 20;
+        }
+        if (startDate) {
+            config.semesterStartDate = startDate;
+        }
+        await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
+
+        return true;
+    } catch (e) {
+        window.shiguangBridge.showToast("保存失败: " + e.message);
+        return false;
+    }
+}
+
+// 流程控制
+
+async function runImportFlow() {
+    const baseUrl = getBaseUrl();
+    console.log("使用域名:", baseUrl);
+
+    const alertResult = await promptUserToStart();
+    if (!alertResult) return;
+
+    // 获取学期列表
+    window.shiguangBridge.showToast("正在获取学期列表...");
+    const indexResult = await fetchIndexPage(baseUrl);
+    if (!indexResult) return;
+
+    // 选择学期
+    const selected = await selectSemesterFromList(indexResult.semesterList, indexResult.defaultIndex);
+    if (selected === null) {
+        window.shiguangBridge.showToast("已取消");
+        return;
+    }
+
+    // 获取开学日期(从校历页面自动获取,用户可修改)
+    window.shiguangBridge.showToast("正在获取校历信息...");
+    let startDate = await fetchStartDate(baseUrl);
+    if (startDate) {
+        startDate = await getStartDate(startDate);
+        if (startDate === null) {
+            window.shiguangBridge.showToast("已取消");
+            return;
+        }
+    } else {
+        startDate = await getStartDate("");
+        if (startDate === null) {
+            window.shiguangBridge.showToast("已取消");
+            return;
+        }
+    }
+
+    // 获取课程和时间段
+    window.shiguangBridge.showToast("正在获取数据...");
+    const [courses, timeSlotResult] = await Promise.all([
+        fetchCourses(baseUrl, indexResult.ajaxUrl, selected.value),
+        fetchTimeSlots(baseUrl)
+    ]);
+
+    if (!courses || courses.length === 0) {
+        window.shiguangBridge.showToast("未获取到课程数据");
+        return;
+    }
+
+    const timeSlots = timeSlotResult ? timeSlotResult.timeSlots : null;
+    const totalWeeks = timeSlotResult ? timeSlotResult.totalWeeks : null;
+
+    // 保存数据
+    const saveResult = await saveToApp(courses, timeSlots, startDate, totalWeeks);
+    if (!saveResult) return;
+
+    window.shiguangBridge.showToast(`成功导入 ${courses.length} 个课程时段`);
+    window.shiguangBridge.notifyTaskCompletion();
+}
+
+// 启动
+runImportFlow();