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

Merge pull request #586 from XingHeYuZhuan/pending

星河欲转 2 недель назад
Родитель
Сommit
14758b5859
5 измененных файлов с 355 добавлено и 63 удалено
  1. 5 0
      index/root_index.yaml
  2. 8 0
      resources/GDIT/adapters.yaml
  3. 230 0
      resources/GDIT/gdit.js
  4. 84 63
      resources/GDUT/gdut.js
  5. 28 0
      resources/HIT/hit.js

+ 5 - 0
index/root_index.yaml

@@ -703,6 +703,11 @@ schools:
     initial: "G"
     resource_folder: "GDOU"
 
+  - id: "GDIT"
+    name: "广东科学技术职业学院"
+    initial: "G"
+    resource_folder: "GDIT"
+
   - id: "GUC"
     name: "吉利学院"
     initial: "J"

+ 8 - 0
resources/GDIT/adapters.yaml

@@ -0,0 +1,8 @@
+adapters:
+  - adapter_id: "GDIT_01"
+    adapter_name: "广东科学技术职业学院教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "gdit.js"
+    import_url: "https://ids.gdit.edu.cn/authserver/login?service=https://wbdt.gdit.edu.cn/shiro-cas"
+    maintainer: "Haooz"
+    description: "广东科学技术职业学院网上办事大厅课程表导入适配。登录后进入课程表页面即可导入。"

+ 230 - 0
resources/GDIT/gdit.js

@@ -0,0 +1,230 @@
+// 文件: gdit.js
+// 广东科学技术职业学院 课程表导入适配脚本
+
+if (typeof Strings === 'undefined') {
+    var Strings = {
+        BASE_URL: "https://wbdt.gdit.edu.cn",
+        GET_STUDENT_SCHEDULE_URL: "front/extraCard/queryStudentSchedule"
+    };
+}
+
+async function stepDescriptionAlert() {
+    try {
+        const confirmed = await window.shiguangBridgePromise.showAlert(
+            "提示",
+            "即将执行导入课程操作。请确保你已处于登录状态,并已打开课程表页面。",
+            "确认"
+        );
+        return confirmed;
+    } catch (error) {
+        console.error("显示弹窗时发生错误:", error);
+        return false;
+    }
+}
+
+async function selectSemesterSelection() {
+    const now = new Date();
+    const currentYear = now.getFullYear();
+    const currentMonth = now.getMonth() + 1;
+    const currentSemester = currentMonth >= 9 || currentMonth <= 2 ? 1 : 2;
+
+    const presetSemesters = [];
+    const presetSemestersName = [];
+
+    for (let year = currentYear; year >= currentYear - 3; year--) {
+        for (let semester = (year === currentYear ? currentSemester : 2); semester >= 1; semester--) {
+            const semesterName = `${year}-${year + 1}学年 第${semester}学期`;
+            presetSemesters.push({
+                name: semesterName,
+                schoolYear: `${year}-${year + 1}`,
+                semester: String(semester)
+            });
+            presetSemestersName.push(semesterName);
+        }
+    }
+
+    try {
+        const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
+            "选择要导入的学期",
+            JSON.stringify(presetSemestersName),
+            0
+        );
+
+        if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < presetSemesters.length) {
+            const selected = presetSemesters[selectedIndex];
+            console.log("用户选择了: " + selected.name);
+            return selected;
+        } else {
+            console.log("用户取消了选择。");
+            return null;
+        }
+    } catch (error) {
+        console.error("显示单选列表弹窗时发生错误:", error);
+        window.shiguangBridge.showToast("显示列表出错!" + error.message);
+        return null;
+    }
+}
+
+async function fetchCourses(schoolYear, semester) {
+    try {
+        console.log(`正在获取 ${schoolYear} 第${semester}学期 的课程数据...`);
+
+        const timestamp = Date.now();
+        const url = `${Strings.BASE_URL}/${Strings.GET_STUDENT_SCHEDULE_URL}?_t=${timestamp}&semester=${semester}&schoolYear=${schoolYear}`;
+
+        const response = await fetch(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            credentials: 'include'
+        });
+
+        if (!response.ok) {
+            throw new Error(`请求失败: ${response.status}`);
+        }
+
+        const result = await response.json();
+        console.log("API 返回数据:", result);
+
+        if (!result.meta || !result.meta.success) {
+            throw new Error(result.meta ? result.meta.message : "请求失败");
+        }
+
+        if (!result.data || !Array.isArray(result.data) || result.data.length === 0) {
+            throw new Error("该学期没有课程数据");
+        }
+
+        const courses = parseCourses(result.data);
+        return courses;
+    } catch (error) {
+        console.error('获取课程数据失败:', error);
+        window.shiguangBridge.showToast("获取课程失败: " + error.message);
+        return null;
+    }
+}
+
+function parseCourses(rawCourses) {
+    console.log(`正在解析课程数据,共 ${rawCourses.length} 条原始记录...`);
+
+    const courses = [];
+
+    for (const raw of rawCourses) {
+        try {
+            const name = raw.courseName || "";
+            const teacher = raw.userName || "";
+            const position = raw.placeName || "";
+
+            const day = Number(raw.weekDay);
+            if (isNaN(day) || day < 1 || day > 7) continue;
+
+            const sectionStr = raw.festivals || "";
+            const sectionMatch = sectionStr.match(/(\d+)-(\d+)/);
+            if (!sectionMatch) continue;
+            const startSection = Number(sectionMatch[1]);
+            const endSection = Number(sectionMatch[2]);
+
+            const startWeek = Number(raw.startWeek);
+            const endWeek = Number(raw.endWeek);
+            if (isNaN(startWeek) || isNaN(endWeek)) continue;
+
+            const singleDoubleWeek = raw.singleDoubleWeek || "0";
+            const weeks = [];
+            for (let w = startWeek; w <= endWeek; w++) {
+                if (singleDoubleWeek === "1" && w % 2 === 0) continue;
+                if (singleDoubleWeek === "2" && w % 2 !== 0) continue;
+                weeks.push(w);
+            }
+
+            if (name && weeks.length > 0) {
+                courses.push({
+                    name: name.trim(),
+                    teacher: teacher.trim(),
+                    position: position.trim(),
+                    day: day,
+                    startSection: startSection,
+                    endSection: endSection,
+                    weeks: weeks,
+                    isCustomTime: false
+                });
+            }
+        } catch (e) {
+            console.warn("解析单条课程数据失败:", e, raw);
+        }
+    }
+
+    console.log(`成功解析 ${courses.length} 门课程`);
+    return courses;
+}
+
+async function saveCourses(courses) {
+    try {
+        console.log("正在尝试导入课程...");
+        const result = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (result === true) {
+            console.log("课程导入成功!");
+        } else {
+            console.log("课程导入未成功,结果:" + result);
+            window.shiguangBridge.showToast("课程导入失败,请查看日志。");
+        }
+    } catch (error) {
+        console.error("导入课程时发生错误:", error);
+        window.shiguangBridge.showToast("导入课程失败: " + error.message);
+    }
+}
+
+async function setPresetTimeSlots() {
+    const presetTimeSlots = [
+        { "number": 1, "startTime": "08:10", "endTime": "08:55" },
+        { "number": 2, "startTime": "09:05", "endTime": "09:50" },
+        { "number": 3, "startTime": "10:10", "endTime": "10:55" },
+        { "number": 4, "startTime": "11:05", "endTime": "11:50" },
+        { "number": 5, "startTime": "14:30", "endTime": "15:15" },
+        { "number": 6, "startTime": "15:25", "endTime": "16:10" },
+        { "number": 7, "startTime": "16:30", "endTime": "17:15" },
+        { "number": 8, "startTime": "17:25", "endTime": "18:10" },
+        { "number": 9, "startTime": "19:10", "endTime": "19:55" },
+        { "number": 10, "startTime": "20:05", "endTime": "20:50" },
+        { "number": 11, "startTime": "21:00", "endTime": "21:45" }
+    ];
+
+    try {
+        console.log("正在尝试导入预设时间段...");
+        const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
+        if (result === true) {
+            console.log("预设时间段导入成功!");
+        } else {
+            console.log("预设时间段导入未成功,结果:" + result);
+        }
+    } catch (error) {
+        console.error("导入时间段时发生错误:", error);
+    }
+}
+
+async function runImportFlow() {
+    const confirmed = await stepDescriptionAlert();
+    if (!confirmed) {
+        console.log("用户取消了操作。");
+        return;
+    }
+
+    const semester = await selectSemesterSelection();
+    if (!semester) {
+        console.log("用户取消了学期选择。");
+        return;
+    }
+
+    const courses = await fetchCourses(semester.schoolYear, semester.semester);
+    if (!courses || courses.length === 0) {
+        window.shiguangBridge.showToast("未找到课程数据,请确认选择了正确的学期");
+        return;
+    }
+
+    await saveCourses(courses);
+    await setPresetTimeSlots();
+
+    window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
+    window.shiguangBridge.notifyTaskCompletion();
+}
+
+runImportFlow();

+ 84 - 63
resources/GDUT/gdut.js

@@ -1,10 +1,10 @@
 // 文件: gdut.js
 
 if (typeof Strings === 'undefined') {
-    var Strings = {
+    const Strings = {
         BASE_URL: "https://jxfw.gdut.edu.cn",
-        GET_WEEK_COURSES_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!getKbRq.action",
-        GET_ALL_COURSES_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!getDataList.action",
+        GET_WEEK_COURSES_API_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!getKbRq.action",
+        GET_ALL_COURSES_API_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!getDataList.action",
         GET_ALL_COURSES_HTML_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!xsAllKbList.action",
         GET_ALL_COURSES_HTML_URL_REFERRER: "https://jxfw.gdut.edu.cn/xsgrkbcx!getXsgrbkList.action"
     };
@@ -14,7 +14,7 @@ async function stepDescriptionAlert() {
     try {
         const confirmed = await window.shiguangBridgePromise.showAlert(
             "提示",
-            "即将执行导入课程操作。请确保你已处于登录状态,无需打开课程表页面。",
+            "即将执行导入课程操作,确保当前处于已登录状态(无需打开课程表页面)",
             "确认"
         );
         
@@ -29,7 +29,7 @@ async function selectSemesterSelection(){
     const now = new Date();
     const currentYear = now.getFullYear();
     const currentMonth = now.getMonth() + 1;
-    const currentSemester = currentMonth >= 8 || currentMonth <= 2 ? 1 : 2;
+    const currentSemester = currentMonth >= 7 || currentMonth <= 1 ? 1 : 2;
 
     const nextSemester = currentSemester === 1 ? 2 : 1;
     const nextSemesterYear = currentSemester === 1 ? currentYear : currentYear + 1;
@@ -40,7 +40,7 @@ async function selectSemesterSelection(){
     for (let year = nextSemesterYear; year >= nextSemesterYear - 3; year--){
         for (let semester = nextSemester; semester >= 1; semester--){
             presetSemetersId.push(`${year}0${semester}`);
-            const semesterName = `${year}-${year + 1}学年 ${semester === 1 ? "秋季" : "春季"}(第${semester}学期)`;
+            const semesterName = `${year} 年${semester === 1 ? "秋季" : "春季"} (${year}-${year + 1} 学年第${semester}学期)`;
             presetSemestersName.push(semesterName);
         }
     }
@@ -49,7 +49,7 @@ async function selectSemesterSelection(){
         const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
             "选择要导入的学期",
             JSON.stringify(presetSemestersName),
-            2
+            1
         );
         if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < presetSemetersId.length) {
             console.log("用户选择了: " + presetSemestersName[selectedIndex] + " (索引: " + selectedIndex + ")");
@@ -70,10 +70,6 @@ function extractFirstDay(dateInfoJsonData) {
         const jsonArray = JSON.parse(dateInfoJsonData);
 
         const dateInfoArray = jsonArray[1];
-        if (!Array.isArray(dateInfoArray)) {
-            console.error('JSON 数据格式异常:索引 [1] 不是数组');
-            return null;
-        }
 
         // 遍历查找 xqmc === "1"(周一)的项
         for (const dateInfo of dateInfoArray) {
@@ -82,7 +78,7 @@ function extractFirstDay(dateInfoJsonData) {
             }
         }
 
-        console.warn('未找到 xqmc=1 的日期项');
+        console.error('未找到 xqmc=1 的日期项');
         return null;
     } catch (error) {
         console.error('解析 JSON 失败:', error);
@@ -91,11 +87,10 @@ function extractFirstDay(dateInfoJsonData) {
 }
 
 async function fetchStartDate(semesterId) {
-    const url = `${"https://jxfw.gdut.edu.cn"}/xsgrkbcx!getKbRq.action?xnxqdm=${semesterId}&zc=1`;
+    const url = `${Strings.GET_WEEK_COURSES_API_URL}?xnxqdm=${semesterId}&zc=1`;
     
     try {
-
-        console.log(`正在获取学期开始日期。学期代码:${semesterId}}`);
+        console.log(`正在获取学期开始日期。学期代码:${semesterId}`);
         const response = await fetch(url, {
             method: 'GET',
             headers: {
@@ -136,55 +131,62 @@ async function fetchCourses(semesterId){
 
         const rawCourses = [];
 
-        const pageSize = 100;
-        let pageIndex = 1;
-
-        while (true) {
-            const formData = new URLSearchParams();
-            formData.append('xnxqdm', semesterId);
-            formData.append('zc', '');
-            formData.append('page', String(pageIndex));
-            formData.append('rows', String(pageSize));
-            formData.append('sort', 'kxh');
-            formData.append('order', 'asc');
-
-            const response = await fetch(Strings.GET_ALL_COURSES_URL, {
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/x-www-form-urlencoded',
-                    'Referer': Strings.BASE_URL
-                },
-                body: formData.toString(),
-                credentials: 'include'
-            });
-
-            if (!response.ok) {
-                throw new Error(`请求失败: ${response.status}`);
-            }
+        // 理论上此处应分页遍历处理,但分页处理将导致教务系统返回错误的数据(课程重复和缺失)。
+        // 此处假设总课程数量总是小于 1000,并设置一页的最大课程数量为 1000。
+        const pageSize = 1000;
 
-            const rawData = await response.json();
+        const formData = new URLSearchParams();
+        formData.append('zc', '');
+        formData.append('xnxqdm', semesterId);
+        formData.append('page', '1');
+        formData.append('rows', String(pageSize));
+        formData.append('sort', 'kxh');
+        formData.append('order', 'asc');
 
-            if (rawData.total === 0 || rawData.rows.length === 0) {
-                console.log(`学期 ${semesterId} 没有找到课程数据。`);
-                if (await checkSemesterIsOpened(semesterId)) {
-                    throw new Error('该学期没有找到课程,请确认选择了正确的学期');
-                }
-                throw new Error('学期未开放课表查询!');
-            }
+        const response = await fetch(Strings.GET_ALL_COURSES_API_URL, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'Referer': Strings.BASE_URL
+            },
+            body: formData.toString(),
+            credentials: 'include'
+        });
 
-            for (const row of rawData.rows) {
-                rawCourses.push(row);
-            }
+        if (!response.ok) {
+            throw new Error(`请求失败: ${response.status}`);
+        }
 
-            if (rawCourses.length >= rawData.total) {
-                break;
-            }
+        const rawData = await response.json();
 
-            pageIndex++;
+        if (rawData.total === 0 || rawData.rows.length === 0) {
+            console.log(`学期 ${semesterId} 没有找到课程数据。`);
+            if (await checkSemesterIsOpened(semesterId)) {
+                throw new Error('该学期没有找到课程!请确认选择了正确的学期。');
+            }
+            throw new Error('学期未开放课表查询!');
+        }
 
-            await new Promise(resolve => setTimeout(resolve, 100));
+        for (const row of rawData.rows) {
+            for (const existingCourse of rawCourses) {
+                if (row.xq === existingCourse.xq 
+                    && row.zc === existingCourse.zc 
+                    && row.kcmc === existingCourse.kcmc 
+                    && row.jcdm === existingCourse.jcdm 
+                    && row.kxh === existingCourse.kxh) {
+                    console.error(`检查到有与现有课程重复的课程: `);
+                    console.error(row);
+                    console.error(`已有课程:`);
+                    console.error(existingCourse);
+                    window.shiguangBridge.showToast(`导入课程失败: ${row.kcmc} 意外地出现了重复的课程。请联系开发者解决此问题。`);
+                    return null;
+                }
+            }
+            rawCourses.push(row);
         }
 
+        console.log(`成功获取学期 ${semesterId} 的课程数据,共 ${rawCourses.length} 条记录。`);
+
         const courses = parseCourses(rawCourses);
 
         return courses;
@@ -216,22 +218,29 @@ async function checkSemesterIsOpened(semesterId) {
 }
 
 function parseCourses(rawCourses){
-    console.log(`正在解析课程数据,共 ${rawCourses.length} 条原始记录...`);
+    console.log(`正在转换原始课程数据...`);
 
     const courses = [];
     for (const raw of rawCourses) {
+
         const sectionMatch = raw.jcdm.match(/\d{2}/g);
-        if (!sectionMatch) continue;
-        
+        if (!sectionMatch) {
+            console.error(`课程节次解析失败,原始数据:${raw.jcdm},跳过该课程。`);
+            continue;
+        }
+
         const sections = sectionMatch.map(Number);
         const startSection = sections[0];
         const endSection = sections[sections.length - 1];
         
         // 周次
         const week = Number(raw.zc);
-        if (isNaN(week)) continue;
+        if (isNaN(week)) {
+            console.error(`课程周次解析失败,原始数据:${raw.zc},跳过该课程。`);
+            continue;
+        }
 
-        courses.push({
+        const course = {
             name: raw.kcmc.trim(),
             teacher: (raw.teaxms || "").trim(),
             position: (raw.jxcdmc || "").trim(),
@@ -240,7 +249,9 @@ function parseCourses(rawCourses){
             endSection: endSection,
             weeks: [week],
             isCustomTime: false
-        });
+        };
+
+        courses.push(course);
     }
 
     return courses;
@@ -318,7 +329,17 @@ async function saveConfig(config) {
  * 编排这些异步操作,并在用户取消时停止后续执行。
  */
 async function runImportFlow() {
-    var result = await stepDescriptionAlert();
+    if (window.location.hostname === "authserver.gdut.edu.cn"){
+        window.shiguangBridge.showToast("执行导入课表操作前,必须先登录到教务系统!");
+        return;
+    } 
+
+    if (window.location.hostname !== "jxfw.gdut.edu.cn") {
+        window.shiguangBridge.showToast("当前页面不是教务系统页面!");
+        return;
+    }
+
+    const result = await stepDescriptionAlert();
 
     if (!result) {
         console.log("用户取消了操作,停止后续执行。");

+ 28 - 0
resources/HIT/hit.js

@@ -196,6 +196,32 @@ async function saveCourses(courses) {
     }
 }
 
+async function setPresetTimeSlots() {
+    const presetTimeSlots = [
+        { "number": 1, "startTime": "08:00", "endTime": "08:50" },
+        { "number": 2, "startTime": "08:55", "endTime": "09:45" },
+        { "number": 3, "startTime": "10:00", "endTime": "10:50" },
+        { "number": 4, "startTime": "10:55", "endTime": "11:45" },
+        { "number": 5, "startTime": "13:45", "endTime": "14:35" },
+        { "number": 6, "startTime": "14:40", "endTime": "15:30" },
+        { "number": 7, "startTime": "15:45", "endTime": "16:35" },
+        { "number": 8, "startTime": "16:40", "endTime": "17:30" },
+        { "number": 9, "startTime": "18:30", "endTime": "19:20" },
+        { "number": 10, "startTime": "19:25", "endTime": "20:15" },
+        { "number": 11, "startTime": "20:30", "endTime": "21:20" },
+        { "number": 12, "startTime": "21:25", "endTime": "22:15" }
+    ];
+
+    try {
+        const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
+        if (result === true) {
+            console.log("HIT: 预设时间段导入成功");
+        }
+    } catch (error) {
+        console.error("HIT: 导入时间段失败:", error);
+    }
+}
+
 async function runImportFlow() {
     const alertConfirmed = await promptUserToStart();
     if (!alertConfirmed) {
@@ -231,6 +257,8 @@ async function runImportFlow() {
     const saveResult = await saveCourses(courses);
     if (!saveResult) return;
 
+    await setPresetTimeSlots();
+
     if (semesterInfo && semesterInfo.ZCJSSJ) {
         try {
             await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({