|
@@ -0,0 +1,430 @@
|
|
|
|
|
+// 大连理工大学本科教务系统适配脚本
|
|
|
|
|
+// 适配人: dffzcqs78694
|
|
|
|
|
+//
|
|
|
|
|
+// 接口链路(与页面实际请求一致):
|
|
|
|
|
+// 1. GET /student/for-std/course-table → 解析学期下拉框 + 提取 stdPersonId
|
|
|
|
|
+// 2. GET /student/ws/schedule-table/get-data?bizTypeId=2&semesterId={id} → lessonIds / timeTableLayoutId / weekIndices
|
|
|
|
|
+// 3. POST /student/ws/schedule-table/datum → lessonList(课程) + scheduleList(排课明细)
|
|
|
|
|
+// 4. POST /student/ws/schedule-table/timetable-layout → 节次时间定义
|
|
|
|
|
+// 5. GET /student/ws/semester/get/{semesterId} → 学期起止日期(用于推算开学日期, 失败则回退)
|
|
|
|
|
+
|
|
|
|
|
+// ---- 节次时间表兜底数据(接口失败时使用, 与大工 timetable-layout 一致) ----
|
|
|
|
|
+const FALLBACK_TIME_SLOTS = [
|
|
|
|
|
+ { "number": 1, "startTime": "08:00", "endTime": "08:45" },
|
|
|
|
|
+ { "number": 2, "startTime": "08:50", "endTime": "09:35" },
|
|
|
|
|
+ { "number": 3, "startTime": "10:05", "endTime": "10:50" },
|
|
|
|
|
+ { "number": 4, "startTime": "10:55", "endTime": "11:40" },
|
|
|
|
|
+ { "number": 5, "startTime": "13:30", "endTime": "14:15" },
|
|
|
|
|
+ { "number": 6, "startTime": "14:20", "endTime": "15:05" },
|
|
|
|
|
+ { "number": 7, "startTime": "15:35", "endTime": "16:20" },
|
|
|
|
|
+ { "number": 8, "startTime": "16:25", "endTime": "17:10" },
|
|
|
|
|
+ { "number": 9, "startTime": "18:00", "endTime": "18:45" },
|
|
|
|
|
+ { "number": 10, "startTime": "18:50", "endTime": "19:35" },
|
|
|
|
|
+ { "number": 11, "startTime": "19:40", "endTime": "20:25" },
|
|
|
|
|
+ { "number": 12, "startTime": "20:30", "endTime": "21:15" }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// 时间数字格式化: 1800 -> "18:00"
|
|
|
|
|
+function formatTimeNum(timeNum) {
|
|
|
|
|
+ if (timeNum === null || timeNum === undefined) return "";
|
|
|
|
|
+ const str = String(timeNum).padStart(4, '0');
|
|
|
|
|
+ return `${str.slice(0, 2)}:${str.slice(2, 4)}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断时间数字是否在 [start, end] 区间内(用于节次映射)
|
|
|
|
|
+function timeInRange(timeNum, startNum, endNum) {
|
|
|
|
|
+ return timeNum >= startNum && timeNum <= endNum;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 构建节次列表(数字格式): 优先用接口返回的 courseUnitList, 失败时用内置兜底作息表
|
|
|
|
|
+function buildCourseUnits(layout) {
|
|
|
|
|
+ if (layout && Array.isArray(layout.courseUnitList) && layout.courseUnitList.length > 0) {
|
|
|
|
|
+ return layout.courseUnitList;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 兜底: 内置节次表(与大工 timetable-layout 实际数据一致)
|
|
|
|
|
+ return FALLBACK_TIME_SLOTS.map(slot => ({
|
|
|
|
|
+ indexNo: slot.number,
|
|
|
|
|
+ startTime: parseInt(slot.startTime.replace(':', ''), 10),
|
|
|
|
|
+ endTime: parseInt(slot.endTime.replace(':', ''), 10)
|
|
|
|
|
+ }));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 纯函数: 根据课程原始排课明细解析为拾光课程格式 ----
|
|
|
|
|
+// printData: datum 响应中的 result 对象
|
|
|
|
|
+// layout: timetable-layout 响应中的 result 对象
|
|
|
|
|
+// 返回课程数组
|
|
|
|
|
+function parseCourses(printData, layout) {
|
|
|
|
|
+ const lessonList = printData.lessonList || [];
|
|
|
|
|
+ const scheduleList = printData.scheduleList || [];
|
|
|
|
|
+ const lessonById = {};
|
|
|
|
|
+ lessonList.forEach(lesson => { lessonById[lesson.id] = lesson; });
|
|
|
|
|
+
|
|
|
|
|
+ // 从 layout 中构建节次列表 (用于 startTime/endTime -> 节次号)
|
|
|
|
|
+ const units = buildCourseUnits(layout);
|
|
|
|
|
+
|
|
|
|
|
+ // 按 (lessonId, weekday, startTime, endTime) 分组, 收集周次
|
|
|
|
|
+ const groups = {};
|
|
|
|
|
+ scheduleList.forEach(schedule => {
|
|
|
|
|
+ const key = `${schedule.lessonId}|${schedule.weekday}|${schedule.startTime}|${schedule.endTime}`;
|
|
|
|
|
+ if (!groups[key]) {
|
|
|
|
|
+ groups[key] = { schedule: schedule, weeks: [] };
|
|
|
|
|
+ }
|
|
|
|
|
+ if (schedule.weekIndex !== null && schedule.weekIndex !== undefined) {
|
|
|
|
|
+ groups[key].weeks.push(schedule.weekIndex);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const courses = [];
|
|
|
|
|
+ Object.keys(groups).forEach(key => {
|
|
|
|
|
+ const group = groups[key];
|
|
|
|
|
+ const schedule = group.schedule;
|
|
|
|
|
+ const lesson = lessonById[schedule.lessonId] || {};
|
|
|
|
|
+
|
|
|
|
|
+ // 星期几
|
|
|
|
|
+ const day = schedule.weekday;
|
|
|
|
|
+
|
|
|
|
|
+ // 节次映射: 找到所有 startTime>=调度开始 && endTime<=调度结束 的节次
|
|
|
|
|
+ let startSection = null;
|
|
|
|
|
+ let endSection = null;
|
|
|
|
|
+ if (units.length > 0 && schedule.startTime !== null && schedule.endTime !== null) {
|
|
|
|
|
+ const matched = units.filter(unit =>
|
|
|
|
|
+ timeInRange(unit.startTime, schedule.startTime, schedule.endTime) &&
|
|
|
|
|
+ timeInRange(unit.endTime, schedule.startTime, schedule.endTime)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (matched.length > 0) {
|
|
|
|
|
+ startSection = matched[0].indexNo;
|
|
|
|
|
+ endSection = matched[matched.length - 1].indexNo;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 教师: 优先取排课明细中的教师, 否则取课程下的教师列表
|
|
|
|
|
+ let teacher = schedule.personName || "";
|
|
|
|
|
+ if (!teacher && lesson.teacherAssignmentList) {
|
|
|
|
|
+ teacher = lesson.teacherAssignmentList.map(t => t.name).filter(Boolean).join(" ");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 教室
|
|
|
|
|
+ let position = "";
|
|
|
|
|
+ if (schedule.room && schedule.room.nameZh) {
|
|
|
|
|
+ position = schedule.room.nameZh;
|
|
|
|
|
+ } else if (schedule.customPlace) {
|
|
|
|
|
+ position = schedule.customPlace;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 周次去重排序
|
|
|
|
|
+ const weeks = Array.from(new Set(group.weeks)).sort((a, b) => a - b);
|
|
|
|
|
+
|
|
|
|
|
+ const course = {
|
|
|
|
|
+ name: lesson.courseName || "",
|
|
|
|
|
+ teacher: teacher,
|
|
|
|
|
+ position: position,
|
|
|
|
|
+ day: day,
|
|
|
|
|
+ startSection: startSection,
|
|
|
|
|
+ endSection: endSection,
|
|
|
|
|
+ weeks: weeks
|
|
|
|
|
+ };
|
|
|
|
|
+ courses.push(course);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return courses;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 纯函数: 生成预设时间段 ----
|
|
|
|
|
+// layout: timetable-layout 响应中的 result 对象
|
|
|
|
|
+function buildTimeSlots(layout) {
|
|
|
|
|
+ if (layout && Array.isArray(layout.courseUnitList) && layout.courseUnitList.length > 0) {
|
|
|
|
|
+ return layout.courseUnitList.map(unit => ({
|
|
|
|
|
+ number: unit.indexNo,
|
|
|
|
|
+ startTime: formatTimeNum(unit.startTime),
|
|
|
|
|
+ endTime: formatTimeNum(unit.endTime)
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ return FALLBACK_TIME_SLOTS;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 纯函数: 推算学期开始日期(第1周周一) ----
|
|
|
|
|
+// 排课明细的 date + weekIndex 已知, 用最小的 date 算出其所在周周一,
|
|
|
|
|
+// 再按 weekIndex 差值回退到第 1 周周一。
|
|
|
|
|
+// 例: 第3周周一=2026-09-14, 则第1周周一 = 09-14 - 2周 = 2026-08-31
|
|
|
|
|
+function calcSemesterStartDate(printData) {
|
|
|
|
|
+ let minDate = null;
|
|
|
|
|
+ let minWeekIndex = null;
|
|
|
|
|
+
|
|
|
|
|
+ (printData.scheduleList || []).forEach(s => {
|
|
|
|
|
+ if (s.date && s.date.length >= 10 && s.weekIndex !== null && s.weekIndex !== undefined) {
|
|
|
|
|
+ if (!minDate || s.date < minDate) {
|
|
|
|
|
+ minDate = s.date;
|
|
|
|
|
+ minWeekIndex = s.weekIndex;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!minDate || !minWeekIndex) return null;
|
|
|
|
|
+
|
|
|
|
|
+ // 该日期所在周的周一
|
|
|
|
|
+ const dt = new Date(minDate);
|
|
|
|
|
+ const day = dt.getDay(); // 0=周日
|
|
|
|
|
+ const diff = (day === 0 ? -6 : 1 - day);
|
|
|
|
|
+ dt.setDate(dt.getDate() + diff);
|
|
|
|
|
+
|
|
|
|
|
+ // 回退到第1周
|
|
|
|
|
+ dt.setDate(dt.getDate() - (minWeekIndex - 1) * 7);
|
|
|
|
|
+ return dt.toISOString().split('T')[0];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 桥接封装 ----
|
|
|
|
|
+async function bridgeSaveCourses(courses) {
|
|
|
|
|
+ const result = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
|
|
+ if (result === true) {
|
|
|
|
|
+ window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ window.shiguangBridge.showToast("课程导入未成功,请查看日志。");
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function bridgeSaveTimeSlots(timeSlots) {
|
|
|
|
|
+ const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
|
|
|
|
|
+ if (result === true) {
|
|
|
|
|
+ window.shiguangBridge.showToast(`成功导入 ${timeSlots.length} 个时间段!`);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ window.shiguangBridge.showToast("时间段导入失败,请查看日志。");
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function bridgeSaveConfig(config) {
|
|
|
|
|
+ const result = await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
|
|
|
|
|
+ if (result === true) {
|
|
|
|
|
+ window.shiguangBridge.showToast("课表配置导入成功!");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ window.shiguangBridge.showToast("课表配置导入失败,请查看日志。");
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 1. 公告 ----
|
|
|
|
|
+async function promptUserToStart() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const confirmed = await window.shiguangBridgePromise.showAlert(
|
|
|
|
|
+ "导入提醒",
|
|
|
|
|
+ "请确保您已登录教务系统,导入前请确认当前学期正确。",
|
|
|
|
|
+ "好的,开始导入"
|
|
|
|
|
+ );
|
|
|
|
|
+ return confirmed === true;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("公告弹窗出错:", e);
|
|
|
|
|
+ window.shiguangBridge.showToast("显示公告出错: " + e.message);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 2. 获取学期列表 ----
|
|
|
|
|
+async function getSemesterOptions() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/student/for-std/course-table`);
|
|
|
|
|
+ const htmlString = await response.text();
|
|
|
|
|
+ const parser = new DOMParser();
|
|
|
|
|
+ const dom = parser.parseFromString(htmlString, 'text/html');
|
|
|
|
|
+ const selectElement = dom.getElementById('semesters') || dom.getElementById('allSemesters');
|
|
|
|
|
+ if (!selectElement) throw new Error("页面中未找到学期选择框");
|
|
|
|
|
+
|
|
|
|
|
+ const options = Array.from(selectElement.options);
|
|
|
|
|
+ const validOptions = options.filter(opt => opt.value && opt.value !== "all");
|
|
|
|
|
+ if (validOptions.length === 0) throw new Error("未解析到有效的学期列表");
|
|
|
|
|
+
|
|
|
|
|
+ const semesterTexts = validOptions.map(opt => opt.text.trim());
|
|
|
|
|
+ const semesterValues = validOptions.map(opt => opt.value);
|
|
|
|
|
+ return { semesterTexts, semesterValues };
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("获取学期列表失败:", e);
|
|
|
|
|
+ window.shiguangBridge.showToast("获取学期列表失败: " + e.message);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 3. 获取 stdPersonId (从页面 JS 变量中提取) ----
|
|
|
|
|
+async function fetchStdPersonId() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/student/for-std/course-table`);
|
|
|
|
|
+ const htmlString = await response.text();
|
|
|
|
|
+ // 常见模式: stdPersonId = 3284653 或 stdPersonId: 3284653 或 stdPersonId":"3284653"
|
|
|
|
|
+ const patterns = [
|
|
|
|
|
+ /stdPersonId\s*[:=]\s*["']?(\d+)["']?/,
|
|
|
|
|
+ /std_person_id\s*[:=]\s*["']?(\d+)["']?/,
|
|
|
|
|
+ /stdPersonId["']?\s*[:=]\s*["']?(\d+)["']?/
|
|
|
|
|
+ ];
|
|
|
|
|
+ for (const pattern of patterns) {
|
|
|
|
|
+ const match = htmlString.match(pattern);
|
|
|
|
|
+ if (match) return match[1];
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("提取 stdPersonId 失败:", e);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 4. 获取课程基础数据 (get-data) ----
|
|
|
|
|
+async function fetchGetData(semesterId) {
|
|
|
|
|
+ const url = `/student/for-std/course-table/get-data?bizTypeId=2&semesterId=${semesterId}`;
|
|
|
|
|
+ const response = await fetch(url);
|
|
|
|
|
+ if (!response.ok) throw new Error(`get-data 请求失败, 状态码: ${response.status}`);
|
|
|
|
|
+ return await response.json();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 5. 获取排课明细 (datum) ----
|
|
|
|
|
+async function fetchDatum(lessonIds, stdPersonId) {
|
|
|
|
|
+ const body = {
|
|
|
|
|
+ lessonIds: lessonIds,
|
|
|
|
|
+ studentId: null,
|
|
|
|
|
+ stdPersonId: stdPersonId,
|
|
|
|
|
+ weekIndex: null
|
|
|
|
|
+ };
|
|
|
|
|
+ const response = await fetch(`/student/ws/schedule-table/datum`, {
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ headers: { "Content-Type": "application/json" },
|
|
|
|
|
+ body: JSON.stringify(body)
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) throw new Error(`datum 请求失败, 状态码: ${response.status}`);
|
|
|
|
|
+ return await response.json();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 6. 获取节次时间表 (timetable-layout) ----
|
|
|
|
|
+async function fetchTimeTableLayout(timeTableLayoutId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/student/ws/schedule-table/timetable-layout`, {
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ headers: { "Content-Type": "application/json" },
|
|
|
|
|
+ body: JSON.stringify({ id: timeTableLayoutId })
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) throw new Error(`timetable-layout 请求失败, 状态码: ${response.status}`);
|
|
|
|
|
+ const json = await response.json();
|
|
|
|
|
+ if (!json || !json.result || !json.result.courseUnitList) throw new Error("timetable-layout 响应结构异常");
|
|
|
|
|
+ return json.result;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn("获取节次时间表失败, 使用内置兜底数据:", e);
|
|
|
|
|
+ return { courseUnitList: null };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 7. 获取学期日期信息 (用于推算开学日期, 可选) ----
|
|
|
|
|
+async function fetchSemesterInfo(semesterId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/student/ws/semester/get/${semesterId}`);
|
|
|
|
|
+ if (!response.ok) return null;
|
|
|
|
|
+ return await response.json();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---- 主流程 ----
|
|
|
|
|
+async function runImportFlow() {
|
|
|
|
|
+ window.shiguangBridge.showToast("课程导入流程即将开始...");
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 公告确认
|
|
|
|
|
+ const alertConfirmed = await promptUserToStart();
|
|
|
|
|
+ if (!alertConfirmed) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 选择学期
|
|
|
|
|
+ const semesterOptions = await getSemesterOptions();
|
|
|
|
|
+ if (!semesterOptions) return;
|
|
|
|
|
+ const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
|
|
+ "选择学期",
|
|
|
|
|
+ JSON.stringify(semesterOptions.semesterTexts),
|
|
|
|
|
+ 0
|
|
|
|
|
+ );
|
|
|
|
|
+ if (selectedIndex === null || selectedIndex < 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("导入已取消。");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const semesterId = semesterOptions.semesterValues[selectedIndex];
|
|
|
|
|
+ window.shiguangBridge.showToast(`已选择: ${semesterOptions.semesterTexts[selectedIndex]}`);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 3. 获取课程基础数据
|
|
|
|
|
+ window.shiguangBridge.showToast("正在获取课程数据...");
|
|
|
|
|
+ const getData = await fetchGetData(semesterId);
|
|
|
|
|
+ const lessonIds = getData.lessonIds || [];
|
|
|
|
|
+ const timeTableLayoutId = getData.timeTableLayoutId;
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 获取 stdPersonId
|
|
|
|
|
+ let stdPersonId = await fetchStdPersonId();
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 获取排课明细
|
|
|
|
|
+ window.shiguangBridge.showToast("正在获取排课明细...");
|
|
|
|
|
+ const datum = await fetchDatum(lessonIds, stdPersonId);
|
|
|
|
|
+ const result = datum.result;
|
|
|
|
|
+ if (!result || !result.lessonList || !result.scheduleList) {
|
|
|
|
|
+ throw new Error("datum 响应结构异常, 请检查是否已登录");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 获取节次时间表
|
|
|
|
|
+ const layout = await fetchTimeTableLayout(timeTableLayoutId);
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 解析课程
|
|
|
|
|
+ const courses = parseCourses(result, layout);
|
|
|
|
|
+ if (courses.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("未解析到课程数据。");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 生成时间段
|
|
|
|
|
+ const timeSlots = buildTimeSlots(layout);
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 生成课表配置
|
|
|
|
|
+ const config = {
|
|
|
|
|
+ semesterStartDate: null,
|
|
|
|
|
+ semesterTotalWeeks: 20,
|
|
|
|
|
+ defaultClassDuration: 45,
|
|
|
|
|
+ defaultBreakDuration: 5,
|
|
|
|
|
+ firstDayOfWeek: 1
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 优先尝试从学期接口获取开学日期与周数
|
|
|
|
|
+ const semesterInfo = await fetchSemesterInfo(semesterId);
|
|
|
|
|
+ if (semesterInfo && semesterInfo.startDate) {
|
|
|
|
|
+ config.semesterStartDate = semesterInfo.startDate;
|
|
|
|
|
+ if (semesterInfo.endDate) {
|
|
|
|
|
+ const start = new Date(semesterInfo.startDate);
|
|
|
|
|
+ const end = new Date(semesterInfo.endDate);
|
|
|
|
|
+ const diffDays = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
|
|
|
|
|
+ const weeks = Math.ceil(diffDays / 7);
|
|
|
|
|
+ if (weeks > 0) config.semesterTotalWeeks = weeks;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 回退: 从排课明细推算开学日期
|
|
|
|
|
+ const startDate = calcSemesterStartDate(result);
|
|
|
|
|
+ if (startDate) config.semesterStartDate = startDate;
|
|
|
|
|
+ // 回退: 从课程实际最大周次推算总周数(避免出现大量空白周)
|
|
|
|
|
+ if (courses.length > 0) {
|
|
|
|
|
+ let maxWeek = 0;
|
|
|
|
|
+ courses.forEach(c => c.weeks.forEach(w => { if (w > maxWeek) maxWeek = w; }));
|
|
|
|
|
+ if (maxWeek > 0) config.semesterTotalWeeks = maxWeek;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 10. 依次保存
|
|
|
|
|
+ const timeSlotOk = await bridgeSaveTimeSlots(timeSlots);
|
|
|
|
|
+ if (!timeSlotOk) return;
|
|
|
|
|
+
|
|
|
|
|
+ const configOk = await bridgeSaveConfig(config);
|
|
|
|
|
+ if (!configOk) return;
|
|
|
|
|
+
|
|
|
|
|
+ const coursesOk = await bridgeSaveCourses(courses);
|
|
|
|
|
+ if (!coursesOk) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 11. 完成
|
|
|
|
|
+ window.shiguangBridge.showToast("课程导入完成!");
|
|
|
|
|
+ window.shiguangBridge.notifyTaskCompletion();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("导入流程发生错误:", e);
|
|
|
|
|
+ window.shiguangBridge.showToast("导入失败: " + e.message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 启动
|
|
|
|
|
+runImportFlow();
|