Эх сурвалжийг харах

Merge branch 'pending' of https://github.com/XingHeYuZhuan/shiguang_warehouse into pending

XingHeYuZhuan 2 долоо хоног өмнө
parent
commit
096f600d10

+ 5 - 0
index/root_index.yaml

@@ -48,6 +48,11 @@ schools:
     initial: "C"
     resource_folder: "CQCST"
 
+  - id: "CQUT"
+    name: "重庆理工大学"
+    initial: "C"
+    resource_folder: "CQUT"
+
   - id: "CUST"
     name: "长春理工大学"
     initial: "C"

+ 9 - 0
resources/CQUT/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/CQUT/adapters.yaml
+adapters:
+  - adapter_id: "CQUT"
+    adapter_name: "重庆理工大学教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "cqut_01.js"
+    import_url: "https://timetable-cfc.cqut.edu.cn/"
+    maintainer: "Dawn Drizzle"
+    description: "适配重庆理工大学教务系统"

+ 189 - 0
resources/CQUT/cqut_01.js

@@ -0,0 +1,189 @@
+/**
+ * 重庆理工大学课表导入脚本
+ * author: Dawn Drizzle
+ */
+const API_BASE = 'https://timetable-cfc.cqut.edu.cn/api/courseSchedule';
+
+// 仅允许在课表站点内执行,避免跨站点误触发
+const checkLogin = () => window.location.hostname === 'timetable-cfc.cqut.edu.cn';
+
+// 统一的接口请求封装:POST + JSON + 携带 Cookie,并在失败时提示原因
+const baseFetch = async (path, body, description) => {
+    const response = await fetch(`${API_BASE}/${path}`, {
+        method: 'POST',
+        credentials: 'include',
+        headers: {
+            'Content-Type': 'application/json',
+        },
+        body: body === undefined ? undefined : JSON.stringify(body),
+    });
+
+    if (!response.ok) {
+        AndroidBridge.showToast(`获取${description}失败,请确认已登录后重试`);
+        throw new Error(`获取${description}失败: ${response.status} ${response.statusText}`);
+    }
+
+    try {
+        return await response.json();
+    } catch (error) {
+        AndroidBridge.showToast(`解析${description}失败,请确认当前页面登录状态`);
+        throw new Error(`解析${description}失败: ${error.message}`);
+    }
+};
+
+// 获取当前登录用户信息(包含 username、校区等)
+const getUserInfo = async () => await baseFetch('getUserInfo', {}, '用户信息');
+
+// 获取指定校区的节次时间表
+const getCampusTimeInfo = async (campusName) => await baseFetch('getCampusTimeInfo', { campusName }, '时间表');
+
+// 获取指定周课程事件列表;weekNum/yearTerm 为空时,接口返回当前学期/当前周信息
+const getWeekEvents = async (userID, weekNum, yearTerm, description) => await baseFetch(
+    'listWeekEvents',
+    {
+        userID: String(userID),
+        weekNum,
+        yearTerm,
+    },
+    description,
+);
+
+// 将接口的节次时间转换为统一结构,并按节次序号升序排序
+const parseTimeSlots = (timeSlots) => timeSlots
+    .slice()
+    .sort((left, right) => Number(left.sessionNum) - Number(right.sessionNum))
+    .map((timeSlot) => ({
+        number: Number(timeSlot.sessionNum) || 0,
+        startTime: timeSlot.startTime ?? '',
+        endTime: timeSlot.endTime ?? '',
+    }));
+
+// 推算学期开始日期(YYYY-MM-DD):使用 weekDayList 第一条的月/日 + yearTerm 中的学年信息
+const parseSemesterStartDate = (yearTerm, weekDayList) => {
+    const firstWeekDate = weekDayList?.[0]?.weekDate;
+
+    if (!yearTerm || !firstWeekDate) {
+        return null;
+    }
+
+    const [startYear, endYear, termPart] = String(yearTerm).split('-');
+    const [month, day] = String(firstWeekDate).split('/').map(Number);
+
+    if (!startYear || !endYear || !termPart || Number.isNaN(month) || Number.isNaN(day)) {
+        return null;
+    }
+
+    const year = termPart === '1' ? Number(startYear) : Number(endYear);
+
+    if (Number.isNaN(year)) {
+        return null;
+    }
+
+    const date = new Date(Date.UTC(year, month - 1, day));
+
+    if (Number.isNaN(date.getTime())) {
+        return null;
+    }
+
+    return date.toISOString().split('T')[0];
+};
+
+// 将接口的 event 解析为课程结构(节次、星期、周次等)
+const parseCourse = (event) => {
+    const sessionList = (event.sessionList ?? []).map(Number).filter((session) => !Number.isNaN(session));
+    const startSection = Number(event.sessionStart) || sessionList[0] || 0;
+    const endSection = sessionList[sessionList.length - 1] || startSection + (Number(event.sessionLast) || 1) - 1;
+
+    return {
+        name: event.eventName ?? '',
+        teacher: event.memberName ?? '',
+        position: event.address ?? '',
+        day: Number(event.weekDay) || 0,
+        startSection,
+        endSection,
+        weeks: (event.weekList ?? []).map(Number).filter((week) => !Number.isNaN(week)),
+    };
+};
+
+// 合并完全相同(课程名/老师/地点/星期/节次范围一致)的课程,将周次去重合并
+const mergeCourses = (events) => {
+    const mergedCourses = new Map();
+
+    for (const event of events) {
+        const course = parseCourse(event);
+        const key = [
+            course.name,
+            course.teacher,
+            course.position,
+            course.day,
+            course.startSection,
+            course.endSection,
+        ].join('||');
+
+        if (!mergedCourses.has(key)) {
+            mergedCourses.set(key, course);
+            continue;
+        }
+
+        const existingCourse = mergedCourses.get(key);
+        existingCourse.weeks = [...new Set([...existingCourse.weeks, ...course.weeks])].sort((left, right) => left - right);
+    }
+
+    return [...mergedCourses.values()];
+};
+
+// 将解析后的结构写入 App(配置、课程列表、节次时间表)
+const saveSchedule = (parsedSchedule) => Promise.all([
+    window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(parsedSchedule.courseConfig)),
+    window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedSchedule.courses)),
+    window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(parsedSchedule.timeSlots)),
+]);
+
+// 主流程:校验页面 → 拉取用户/校区 → 拉取节次/当前学期信息 → 拉取全学期每周课程 → 合并保存
+(async () => {
+    if (!checkLogin()) {
+        AndroidBridge.showToast('请先打开重庆理工大学课表页面并登录');
+        throw new Error('当前不在重庆理工大学课表页面');
+    }
+
+    const userInfo = await getUserInfo();
+    const userID = userInfo?.username;
+    const campusName = userInfo?.userCustomSetting?.campusName;
+
+    if (!userID || !campusName) {
+        AndroidBridge.showToast('未获取到完整的用户信息,请确认已登录');
+        throw new Error('用户信息不完整');
+    }
+
+    const [timeSlotData, currentWeekData] = await Promise.all([
+        getCampusTimeInfo(campusName),
+        getWeekEvents(userID, null, null, '当前学期信息'),
+    ]);
+
+    const yearTerm = currentWeekData?.yearTerm;
+    const weekList = Array.isArray(currentWeekData?.weekList) ? currentWeekData.weekList : [];
+    const currentWeekNum = currentWeekData?.weekNum;
+    const semesterStartDate = parseSemesterStartDate(yearTerm, currentWeekData?.weekDayList);
+
+    if (!yearTerm || weekList.length === 0) {
+        AndroidBridge.showToast('未获取到当前学期信息');
+        throw new Error('当前学期信息不完整');
+    }
+
+    const weekResults = await Promise.all(
+        weekList.map((weekNum) => String(weekNum) === String(currentWeekNum)
+            ? currentWeekData
+            : getWeekEvents(userID, String(weekNum), yearTerm, `第${weekNum}周课程`))
+    );
+
+    await saveSchedule({
+        courseConfig: {
+            semesterStartDate,
+            semesterTotalWeeks: weekList.length,
+        },
+        timeSlots: parseTimeSlots(timeSlotData),
+        courses: mergeCourses(weekResults.flatMap((result) => result?.eventList ?? [])),
+    });
+
+    AndroidBridge.notifyTaskCompletion();
+})();