فهرست منبع

Merge pull request #567 from yukoimi/pending

星河欲转 2 هفته پیش
والد
کامیت
0318f7ea56
3فایلهای تغییر یافته به همراه193 افزوده شده و 0 حذف شده
  1. 5 0
      index/root_index.yaml
  2. 9 0
      resources/JNMC/adapters.yaml
  3. 179 0
      resources/JNMC/jnmc.js

+ 5 - 0
index/root_index.yaml

@@ -357,6 +357,11 @@ schools:
     name: "济南大学"
     initial: "J"
     resource_folder: "UJN"
+  
+  - id: "JNMC"
+    name: "济宁医学院"
+    initial: "J"
+    resource_folder: "JNMC"
 
   - id: "HBGUHX"
     name: "河北地质大学华信学院"

+ 9 - 0
resources/JNMC/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/JNMC/adapters.yaml
+adapters:
+  - adapter_id: "JNMC_01"
+    adapter_name: "济宁医学院教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "jnmc.js"
+    import_url: "http://210.44.16.13/"
+    maintainer: "yukoimi"
+    description: "济宁医学院乘方教务适配,请先登录教务系统后再执行导入"

+ 179 - 0
resources/JNMC/jnmc.js

@@ -0,0 +1,179 @@
+// 济宁医学院教务(乘方教务 · 旧版 .action 接口)适配器
+
+const PRESET_TIME_SLOTS = [
+    { number: 1, startTime: "08:00", endTime: "08:40" },
+    { number: 2, startTime: "08:50", endTime: "09:30" },
+    { number: 3, startTime: "09:50", endTime: "10:30" },
+    { number: 4, startTime: "10:40", endTime: "11:20" },
+    { number: 5, startTime: "11:30", endTime: "12:10" },
+    { number: 6, startTime: "14:30", endTime: "15:10" },
+    { number: 7, startTime: "15:20", endTime: "16:00" },
+    { number: 8, startTime: "16:20", endTime: "17:00" },
+    { number: 9, startTime: "17:10", endTime: "17:50" },
+    { number: 10, startTime: "18:00", endTime: "18:40" },
+    { number: 11, startTime: "19:30", endTime: "20:10" },
+    { number: 12, startTime: "20:20", endTime: "21:00" }
+];
+
+// 周次字符串 "10,7,8,9" → 去重排序的周数组
+function parseWeeks(weekStr) {
+    if (!weekStr) return [];
+    const weeks = weekStr.split(",").map(w => parseInt(w.trim(), 10)).filter(w => !isNaN(w) && w > 0);
+    return [...new Set(weeks)].sort((a, b) => a - b);
+}
+
+// 教室可能为空、含 "\\" 分隔的多教室(如 "B205\206教室")或以 "," 分隔的多场地
+function resolvePosition(raw) {
+    const position = String(raw || "").replace(/\\/g, "/").trim();
+    return position || "待定";
+}
+
+// kbxx 课程 JSON → 拾光课程格式(合并同 key 课程的周次)
+function parseCourseList(kbxx) {
+    if (!Array.isArray(kbxx)) throw new Error("课表接口返回格式不正确");
+    const courseMap = new Map();
+    kbxx.forEach(item => {
+        const day = parseInt(item.xq, 10);
+        const sections = String(item.jcdm2 || "").split(",")
+            .map(s => parseInt(s.trim(), 10)).filter(s => !isNaN(s));
+        const allWeeks = parseWeeks(item.zcs);
+        if (!item.kcmc || sections.length === 0 || allWeeks.length === 0 || isNaN(day) || day < 1 || day > 7) return;
+
+        const course = {
+            name: item.kcmc.trim(),
+            teacher: String(item.teaxms || "").trim() || "未知",
+            position: resolvePosition(item.jxcdmcs),
+            day,
+            startSection: Math.min(...sections),
+            endSection: Math.max(...sections),
+            weeks: allWeeks
+        };
+
+        const key = [course.name, course.teacher, course.position, course.day,
+            course.startSection, course.endSection].join("__");
+        const existing = courseMap.get(key);
+        if (existing) existing.weeks = [...new Set([...existing.weeks, ...course.weeks])].sort((a, b) => a - b);
+        else courseMap.set(key, course);
+    });
+    return Array.from(courseMap.values()).sort((a, b) =>
+        a.day - b.day || a.startSection - b.startSection || a.endSection - b.endSection || a.name.localeCompare(b.name)
+    );
+}
+
+// 读取课表页中的学期下拉框
+function extractSemesterOptions(doc) {
+    const selectElem = doc.getElementById("xnxqdm");
+    if (!selectElem) return null;
+    const semesters = [];
+    const semesterValues = [];
+    let defaultIndex = 0;
+    Array.from(selectElem.querySelectorAll("option")).forEach(option => {
+        if (!option.value) return;
+        semesters.push(option.innerText.trim());
+        semesterValues.push(option.value);
+        if (option.selected || option.hasAttribute("selected")) defaultIndex = semesters.length - 1;
+    });
+    if (semesters.length === 0) return null;
+
+    const start = Math.max(0, defaultIndex - 1);
+    const end = Math.min(semesters.length, defaultIndex + 10);
+    return {
+        semesters: semesters.slice(start, end),
+        semesterValues: semesterValues.slice(start, end),
+        defaultIndex: defaultIndex - start
+    };
+}
+
+// 导入前提示先登录教务系统
+async function promptUserToStart() {
+    return await window.shiguangBridgePromise.showAlert(
+        "济宁医学院教务导入",
+        "请先确保已登录教务系统,再继续导入。",
+        "我已登录"
+    );
+}
+
+// 选择学期
+async function selectSemester(semesterOptions) {
+    const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
+        "选择学期",
+        JSON.stringify(semesterOptions.semesters),
+        semesterOptions.defaultIndex
+    );
+    if (selectedIndex === null || selectedIndex < 0) return null;
+    return {
+        label: semesterOptions.semesters[selectedIndex],
+        value: semesterOptions.semesterValues[selectedIndex]
+    };
+}
+
+// 拉取课表页 HTML(含学期列表)
+async function fetchSchedulePage() {
+    const response = await fetch("/xsgrkbcx!getXsgrbkList.action", { method: "GET", credentials: "include" });
+    if (!response.ok) throw new Error(`无法打开课表页面(HTTP ${response.status})`);
+    return response.text();
+}
+
+// 拉取指定学期课表(HTML 内嵌 var kbxx=[...])
+async function fetchCourseData(xnxqdm) {
+    const response = await fetch(
+        `/xsgrkbcx!xsAllKbList.action?xnxqdm=${encodeURIComponent(xnxqdm)}`,
+        { method: "GET", credentials: "include" }
+    );
+    if (!response.ok) throw new Error(`课表请求失败(HTTP ${response.status})`);
+    const htmlText = await response.text();
+    const match = htmlText.match(/var\s+kbxx\s*=\s*(\[[\s\S]*?\]);/);
+    if (!match) throw new Error("课表数据解析失败,请检查登录状态");
+    let kbxx;
+    try {
+        kbxx = JSON.parse(match[1]);
+    } catch (error) {
+        throw new Error(`课表数据解析失败:${error.message}`);
+    }
+    return kbxx;
+}
+
+// 主流程:提示 → 选学期 → 拉课表 → 保存课程与作息
+async function runImportFlow() {
+    try {
+        const confirmed = await promptUserToStart();
+        if (!confirmed) { window.shiguangBridge.showToast("导入已取消"); return; }
+
+        const pageHtml = await fetchSchedulePage();
+        const semesterOptions = extractSemesterOptions(new DOMParser().parseFromString(pageHtml, "text/html"));
+        if (!semesterOptions) throw new Error("未找到学期列表,请先登录教务系统");
+
+        const semester = await selectSemester(semesterOptions);
+        if (!semester) { window.shiguangBridge.showToast("导入已取消"); return; }
+
+        window.shiguangBridge.showToast(`正在获取 ${semester.label} 的课表...`);
+        const courses = parseCourseList(await fetchCourseData(semester.value));
+
+        if (courses.length === 0) {
+            await window.shiguangBridgePromise.showAlert(
+                "提示",
+                "该学期没有获取到课程数据,请检查登录状态和所选学期。",
+                "确定"
+            );
+            return;
+        }
+
+        await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        try {
+            await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(PRESET_TIME_SLOTS));
+        } catch (error) {
+            window.shiguangBridge.showToast(`课程已导入,作息时间导入失败:${error.message}`);
+        }
+
+        window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
+        window.shiguangBridge.notifyTaskCompletion();
+    } catch (error) {
+        await window.shiguangBridgePromise.showAlert(
+            "导入失败",
+            error.message || String(error),
+            "确定"
+        );
+    }
+}
+
+runImportFlow();