|
@@ -0,0 +1,509 @@
|
|
|
|
|
+// 沈阳科技学院 (syist.edu.cn) 拾光课程表适配脚本
|
|
|
|
|
+// 非该大学开发者适配,开发者无法及时发现问题
|
|
|
|
|
+// 出现问题请提联系开发者或者提交pr更改,这更加快速
|
|
|
|
|
+
|
|
|
|
|
+// 备用默认作息时间表
|
|
|
|
|
+const DEFAULT_TIME_SLOTS = [
|
|
|
|
|
+ { "number": 1, "startTime": "08:20", "endTime": "09:05" },
|
|
|
|
|
+ { "number": 2, "startTime": "09:10", "endTime": "09:55" },
|
|
|
|
|
+ { "number": 3, "startTime": "10:10", "endTime": "10:55" },
|
|
|
|
|
+ { "number": 4, "startTime": "11:00", "endTime": "11:45" },
|
|
|
|
|
+ { "number": 5, "startTime": "13:30", "endTime": "14:15" },
|
|
|
|
|
+ { "number": 6, "startTime": "14:20", "endTime": "15:05" },
|
|
|
|
|
+ { "number": 7, "startTime": "15:15", "endTime": "16:00" },
|
|
|
|
|
+ { "number": 8, "startTime": "16:05", "endTime": "16:50" },
|
|
|
|
|
+ { "number": 9, "startTime": "17:20", "endTime": "18:05" },
|
|
|
|
|
+ { "number": 10, "startTime": "18:15", "endTime": "19:00" },
|
|
|
|
|
+ { "number": 11, "startTime": "19:10", "endTime": "19:55" },
|
|
|
|
|
+ { "number": 12, "startTime": "20:05", "endTime": "20:50" }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 辅助函数:解析周次字符串 "111000..." 为数字数组 [1, 2, 3]
|
|
|
|
|
+ */
|
|
|
|
|
+function parseWeeksFromSkzc(skzc) {
|
|
|
|
|
+ const weeks = [];
|
|
|
|
|
+ const rawSkzc = skzc || '';
|
|
|
|
|
+ for (let i = 0; i < rawSkzc.length; i++) {
|
|
|
|
|
+ if (rawSkzc[i] === '1') {
|
|
|
|
|
+ weeks.push(Number(i + 1));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return weeks;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 清除辅助私有属性并过滤掉无效课程
|
|
|
|
|
+ */
|
|
|
|
|
+function cleanCourses(courses) {
|
|
|
|
|
+ return courses.map(c => {
|
|
|
|
|
+ const { _kbId, _day, _startSection, _endSection, ...cleanCourse } = c;
|
|
|
|
|
+ return cleanCourse;
|
|
|
|
|
+ }).filter(c => c.weeks && c.weeks.length > 0);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 节次合并、周次合并与去重函数
|
|
|
|
|
+ */
|
|
|
|
|
+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;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 将教务系统的课程数据转换成 CourseJsonModel 结构
|
|
|
|
|
+ */
|
|
|
|
|
+function parseSingleCourse(rawCourse) {
|
|
|
|
|
+ const courseName = rawCourse.KCM;
|
|
|
|
|
+ const teacherName = rawCourse.SKJS ? rawCourse.SKJS.split('/')[0] : '';
|
|
|
|
|
+ const position = rawCourse.JASMC;
|
|
|
|
|
+ const day = rawCourse.SKXQ;
|
|
|
|
|
+ const startSection = rawCourse.KSJC;
|
|
|
|
|
+ const endSection = rawCourse.JSJC;
|
|
|
|
|
+ const weeks = parseWeeksFromSkzc(rawCourse.SKZC);
|
|
|
|
|
+
|
|
|
|
|
+ if (!courseName || !day || !startSection || !endSection || weeks.length === 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const course = {
|
|
|
|
|
+ "name": courseName,
|
|
|
|
|
+ "teacher": teacherName,
|
|
|
|
|
+ "position": position || '待定',
|
|
|
|
|
+ "day": parseInt(day),
|
|
|
|
|
+ "startSection": parseInt(startSection),
|
|
|
|
|
+ "endSection": parseInt(endSection),
|
|
|
|
|
+ "weeks": weeks
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 用于匹配调课信息的私有辅助属性
|
|
|
|
|
+ course._kbId = rawCourse.KBID;
|
|
|
|
|
+ course._day = course.day;
|
|
|
|
|
+ course._startSection = course.startSection;
|
|
|
|
|
+ course._endSection = course.endSection;
|
|
|
|
|
+
|
|
|
|
|
+ return course;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 将调课数据应用到已解析的课程列表上
|
|
|
|
|
+ */
|
|
|
|
|
+function applyCourseChanges(parsedCourses, rawChanges) {
|
|
|
|
|
+ let successCount = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (const change of rawChanges) {
|
|
|
|
|
+ const kbID = change.KBID;
|
|
|
|
|
+ const originalTeacher = change.YSKJS ? change.YSKJS.split('/')[0] : '';
|
|
|
|
|
+ const weeksToRemove = parseWeeksFromSkzc(change.SKZC);
|
|
|
|
|
+ let changeApplied = false;
|
|
|
|
|
+
|
|
|
|
|
+ const affectedOriginalCourses = parsedCourses.filter(c =>
|
|
|
|
|
+ c._kbId === kbID &&
|
|
|
|
|
+ c._day === parseInt(change.SKXQ) &&
|
|
|
|
|
+ c._startSection === parseInt(change.KSJC) &&
|
|
|
|
|
+ c._endSection === parseInt(change.JSJC)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (affectedOriginalCourses.length === 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (weeksToRemove.length > 0) {
|
|
|
|
|
+ affectedOriginalCourses.forEach(originalCourse => {
|
|
|
|
|
+ const beforeLength = originalCourse.weeks.length;
|
|
|
|
|
+ originalCourse.weeks = originalCourse.weeks.filter(w => !weeksToRemove.includes(w));
|
|
|
|
|
+ if (originalCourse.weeks.length < beforeLength) {
|
|
|
|
|
+ changeApplied = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const isTimeLocationChange = (change.TKLXDM === '01' || change.TKLXDM === '03');
|
|
|
|
|
+
|
|
|
|
|
+ if (isTimeLocationChange && change.XSKZC && change.XSKXQ && change.XKSJC && change.XJSJC) {
|
|
|
|
|
+ const newWeeks = parseWeeksFromSkzc(change.XSKZC);
|
|
|
|
|
+
|
|
|
|
|
+ if (newWeeks.length > 0) {
|
|
|
|
|
+ const newCourse = {
|
|
|
|
|
+ "name": change.KCM,
|
|
|
|
|
+ "teacher": change.XSKJS ? change.XSKJS.split('/')[0] : originalTeacher,
|
|
|
|
|
+ "position": change.XJASMC || change.JASMC || '待定',
|
|
|
|
|
+ "day": parseInt(change.XSKXQ),
|
|
|
|
|
+ "startSection": parseInt(change.XKSJC),
|
|
|
|
|
+ "endSection": parseInt(change.XJSJC),
|
|
|
|
|
+ "weeks": newWeeks,
|
|
|
|
|
+ "_kbId": kbID,
|
|
|
|
|
+ "_day": parseInt(change.XSKXQ),
|
|
|
|
|
+ "_startSection": parseInt(change.XKSJC),
|
|
|
|
|
+ "_endSection": parseInt(change.XJSJC)
|
|
|
|
|
+ };
|
|
|
|
|
+ parsedCourses.push(newCourse);
|
|
|
|
|
+ changeApplied = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (changeApplied) {
|
|
|
|
|
+ successCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (successCount > 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast(`已应用 ${successCount} 条调课/停课变更。`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return parsedCourses;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 前置提示弹窗
|
|
|
|
|
+ */
|
|
|
|
|
+async function promptUserToStart() {
|
|
|
|
|
+ const confirmed = await window.shiguangBridgePromise.showAlert(
|
|
|
|
|
+ "注意",
|
|
|
|
|
+ "导入前请确保您已在浏览器中成功登录教务系统,且当前页面显示课表系统,否则无法获取数据。",
|
|
|
|
|
+ "好的,开始导入"
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!confirmed) {
|
|
|
|
|
+ window.shiguangBridge.showToast("用户取消了导入。");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 动态获取并选择学期
|
|
|
|
|
+ */
|
|
|
|
|
+async function selectSemester() {
|
|
|
|
|
+ const headers = {
|
|
|
|
|
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
|
+ "x-requested-with": "XMLHttpRequest"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let semesterList = [];
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch("http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/jshkcb/xnxqcx.do", {
|
|
|
|
|
+ headers,
|
|
|
|
|
+ body: "*order=-DM",
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ credentials: "include"
|
|
|
|
|
+ });
|
|
|
|
|
+ const resData = await response.json();
|
|
|
|
|
+ semesterList = resData?.datas?.xnxqcx?.rows || [];
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ window.shiguangBridge.showToast("获取学期列表失败,请检查登录状态。");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (semesterList.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("未查询到学期数据。");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const topSemesters = semesterList.slice(0, 10);
|
|
|
|
|
+ const displayNames = topSemesters.map(item => item.MC || item.DM);
|
|
|
|
|
+
|
|
|
|
|
+ const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
|
|
+ "请选择学期",
|
|
|
|
|
+ JSON.stringify(displayNames),
|
|
|
|
|
+ -1
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (selectedIndex === null || selectedIndex === -1) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return topSemesters[selectedIndex];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取开学日期与总周数配置
|
|
|
|
|
+ */
|
|
|
|
|
+async function fetchSemesterConfig(xn, xq) {
|
|
|
|
|
+ const headers = {
|
|
|
|
|
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
|
+ "x-requested-with": "XMLHttpRequest"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch("http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/jshkcb/cxjcs.do", {
|
|
|
|
|
+ headers,
|
|
|
|
|
+ body: `XN=${xn}&XQ=${xq}`,
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ credentials: "include"
|
|
|
|
|
+ });
|
|
|
|
|
+ const resData = await response.json();
|
|
|
|
|
+ const row = resData?.datas?.cxjcs?.rows?.[0];
|
|
|
|
|
+
|
|
|
|
|
+ if (row) {
|
|
|
|
|
+ const rawDate = row.XQKSRQ;
|
|
|
|
|
+ const startDate = rawDate ? rawDate.split(' ')[0] : null;
|
|
|
|
|
+ const totalWeeks = parseInt(row.ZZC) || 20;
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ semesterStartDate: startDate,
|
|
|
|
|
+ semesterTotalWeeks: totalWeeks
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("Fetch Config Error:", e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ semesterTotalWeeks: 20
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取并保存节次作息时间段(含备用逻辑)
|
|
|
|
|
+ */
|
|
|
|
|
+async function importPresetTimeSlots() {
|
|
|
|
|
+ window.shiguangBridge.showToast("正在获取作息时间...");
|
|
|
|
|
+ const headers = {
|
|
|
|
|
+ "accept": "application/json, text/javascript, */*; q=0.01",
|
|
|
|
|
+ "x-requested-with": "XMLHttpRequest"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let presetTimeSlots = null;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch("http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/jshkcb/jc.do", {
|
|
|
|
|
+ headers,
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ credentials: "include"
|
|
|
|
|
+ });
|
|
|
|
|
+ const resData = await response.json();
|
|
|
|
|
+ const rows = resData?.datas?.jc?.rows || [];
|
|
|
|
|
+
|
|
|
|
|
+ if (rows.length > 0) {
|
|
|
|
|
+ presetTimeSlots = rows.map(item => ({
|
|
|
|
|
+ number: parseInt(item.DM),
|
|
|
|
|
+ startTime: item.KSSJ,
|
|
|
|
|
+ endTime: item.JSSJ
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn("拉取作息时间失败,将使用备用作息时间表:", error);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!presetTimeSlots || presetTimeSlots.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("未能获取线上作息时间,已启用备用作息表。");
|
|
|
|
|
+ presetTimeSlots = DEFAULT_TIME_SLOTS;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
|
|
|
|
|
+ window.shiguangBridge.showToast("预设时间段导入成功!");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.shiguangBridge.showToast("保存时间段失败: " + error.message);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取并解析课程数据
|
|
|
|
|
+ */
|
|
|
|
|
+async function fetchAndParseCourses(semesterObj) {
|
|
|
|
|
+ const XNXQDM = semesterObj.DM;
|
|
|
|
|
+ const headers = {
|
|
|
|
|
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
|
+ "x-requested-with": "XMLHttpRequest"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const courseUrl = "http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/xskcb/cxxszhxqkb.do";
|
|
|
|
|
+ const courseBody = `XNXQDM=${XNXQDM}`;
|
|
|
|
|
+ let rawCourseData;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(courseUrl, { headers, body: courseBody, method: "POST", credentials: "include" });
|
|
|
|
|
+ rawCourseData = await response.json();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ window.shiguangBridge.showToast("请求课表 API 失败,请检查网络或登录状态。");
|
|
|
|
|
+ console.error("Fetch Course Error:", e);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rawCourses = rawCourseData?.datas?.cxxszhxqkb?.rows || [];
|
|
|
|
|
+ if (rawCourses.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("该学期未查询到您的课程数据。");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let parsedCourses = rawCourses.map(c => parseSingleCourse(c)).filter(c => c !== null);
|
|
|
|
|
+
|
|
|
|
|
+ const changeUrl = "http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/xskcb/xsdkkc.do";
|
|
|
|
|
+ const changeBody = `XNXQDM=${XNXQDM}&*order=-SQSJ`;
|
|
|
|
|
+ let rawChangeData;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(changeUrl, { headers, body: changeBody, method: "POST", credentials: "include" });
|
|
|
|
|
+ rawChangeData = await response.json();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ window.shiguangBridge.showToast("请求调课 API 失败,将使用原始课表。");
|
|
|
|
|
+ console.error("Fetch Change Error:", e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rawChanges = rawChangeData?.datas?.xsdkkc?.rows || [];
|
|
|
|
|
+
|
|
|
|
|
+ if (rawChanges.length > 0) {
|
|
|
|
|
+ parsedCourses = applyCourseChanges(parsedCourses, rawChanges);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 清理临时字段
|
|
|
|
|
+ const cleanList = cleanCourses(parsedCourses);
|
|
|
|
|
+ // 2. 执行两阶段合并(节次合并 + 周次合并)
|
|
|
|
|
+ const finalCourses = mergeAndDistinctCourses(cleanList);
|
|
|
|
|
+
|
|
|
|
|
+ const courseConfig = await fetchSemesterConfig(semesterObj.XNDM, semesterObj.XQDM);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ courses: finalCourses,
|
|
|
|
|
+ config: courseConfig
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 保存课程数据
|
|
|
|
|
+ */
|
|
|
|
|
+async function saveCourses(parsedCourses) {
|
|
|
|
|
+ if (!parsedCourses || parsedCourses.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("没有有效的课程数据可供保存。");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
|
|
|
|
|
+ window.shiguangBridge.showToast(`成功导入 ${parsedCourses.length} 门课程!`);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.shiguangBridge.showToast(`保存课程数据失败: ${error.message}`);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 保存配置数据
|
|
|
|
|
+ */
|
|
|
|
|
+async function saveConfig(configData) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(configData));
|
|
|
|
|
+ window.shiguangBridge.showToast("课表配置更新成功!");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.shiguangBridge.showToast("保存配置失败: " + error.message);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 主流程控制
|
|
|
|
|
+ */
|
|
|
|
|
+async function runImportFlow() {
|
|
|
|
|
+ window.shiguangBridge.showToast("课程导入流程启动...");
|
|
|
|
|
+
|
|
|
|
|
+ const alertConfirmed = await promptUserToStart();
|
|
|
|
|
+ if (!alertConfirmed) return;
|
|
|
|
|
+
|
|
|
|
|
+ const selectedSemester = await selectSemester();
|
|
|
|
|
+ if (!selectedSemester) {
|
|
|
|
|
+ window.shiguangBridge.showToast("导入已取消。");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await importPresetTimeSlots();
|
|
|
|
|
+
|
|
|
|
|
+ const courseData = await fetchAndParseCourses(selectedSemester);
|
|
|
|
|
+ if (!courseData) return;
|
|
|
|
|
+
|
|
|
|
|
+ const configSaveResult = await saveConfig(courseData.config);
|
|
|
|
|
+ if (!configSaveResult) return;
|
|
|
|
|
+
|
|
|
|
|
+ const saveResult = await saveCourses(courseData.courses);
|
|
|
|
|
+ if (!saveResult) return;
|
|
|
|
|
+
|
|
|
|
|
+ window.shiguangBridge.showToast("所有任务已完成!课表导入成功。");
|
|
|
|
|
+ window.shiguangBridge.notifyTaskCompletion();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 启动导入流程
|
|
|
|
|
+runImportFlow();
|