|
|
@@ -5,6 +5,19 @@
|
|
|
// 基于GSMC修改
|
|
|
// 作者:洛初 Github@gongfuture
|
|
|
|
|
|
+// 2026.03.30 第一版
|
|
|
+// 通过正方接口 xskbcx_cxXsgrkb 拉取个人课表,解析课程名、教师、教室、星期、节次和周次(含单双周)。
|
|
|
+// 交互上依次询问学年、学期与作息类型(夏令时/冬令时/智能选择,智能选择按固定切令时日期判定)。
|
|
|
+// 按课程所在楼栋匹配上午与下午作息,写入课程的自定义时间;导入课程、课表配置与预设时间段。
|
|
|
+
|
|
|
+// 2026.08.23 第二版
|
|
|
+// 桥接接口升级至 v2(window.shiguangBridge / window.shiguangBridgePromise)。
|
|
|
+// 补充开学日期:从首页日历区块取当前学期起止日期写入 semesterStartDate,总周数改为按实际周次取值;
|
|
|
+// 取不到开学日期时跳过配置保存,避免覆盖用户已有设置。
|
|
|
+// 课程备注补充重修标记、选课备注(体育项目、微专业等)与周次原文。
|
|
|
+// 集中实践课(军训、毕业设计等)无星期节次无法排课,改为弹窗提示手动添加。
|
|
|
+// 教师与教室为空不再丢弃课程;修正京江各号楼的楼栋匹配;支持校外 WebVPN 访问。
|
|
|
+
|
|
|
/**
|
|
|
* 解析周次字符串,处理单双周和周次范围。
|
|
|
*/
|
|
|
@@ -50,6 +63,62 @@ function parseWeeks(weekStr) {
|
|
|
return [...new Set(weeks)].sort((a, b) => a - b);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 拼接教务系统接口地址。
|
|
|
+ * 校内直连时 location.origin 就是教务域名,前缀为空;
|
|
|
+ * 校外经 WebVPN 访问时路径带有 /http/<hex> 前缀,必须保留,否则会变成跨域请求。
|
|
|
+ */
|
|
|
+function buildApiUrl(path) {
|
|
|
+ const prefixMatch = window.location.pathname.match(/^\/http\/[0-9a-f]+/i);
|
|
|
+ const prefix = prefixMatch ? prefixMatch[0] : "";
|
|
|
+ return window.location.origin + prefix + path;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 拼装课程备注。
|
|
|
+ * xm 只有姓名,kcmc 只有课程名,以下信息只存在于原始字段里,
|
|
|
+ * 放进备注方便用户核对:重修标记、选课备注(体育项目、微专业等)、周次原文。
|
|
|
+ */
|
|
|
+function buildCourseRemark(rawCourse) {
|
|
|
+ const parts = [];
|
|
|
+
|
|
|
+ const retakeFlag = String(rawCourse.cxbjmc || "").trim();
|
|
|
+ if (retakeFlag) {
|
|
|
+ parts.push(retakeFlag);
|
|
|
+ }
|
|
|
+
|
|
|
+ const selectionNote = String(rawCourse.xkbz || "").trim();
|
|
|
+ if (selectionNote) {
|
|
|
+ parts.push(selectionNote);
|
|
|
+ }
|
|
|
+
|
|
|
+ const weekDesc = String(rawCourse.zcd || "").trim();
|
|
|
+ if (weekDesc) {
|
|
|
+ parts.push(weekDesc);
|
|
|
+ }
|
|
|
+
|
|
|
+ return parts.join(" | ");
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析集中实践课列表(sjkList)。
|
|
|
+ * 这类课程(军事技能训练、形势与政策等)只有课程名、教师和起止周,
|
|
|
+ * 没有星期和节次,无法映射到周课表,只能提示用户手动添加。
|
|
|
+ */
|
|
|
+function parsePracticeCourses(jsonData) {
|
|
|
+ if (!jsonData || !Array.isArray(jsonData.sjkList)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ return jsonData.sjkList
|
|
|
+ .map((item) => ({
|
|
|
+ name: String(item.kcmc || "").trim(),
|
|
|
+ teacher: String(item.jsxm || "").trim(),
|
|
|
+ weekDesc: String(item.qsjsz || "").trim()
|
|
|
+ }))
|
|
|
+ .filter((item) => item.name);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 解析 API 返回的 JSON 数据。
|
|
|
*/
|
|
|
@@ -66,9 +135,10 @@ function parseJsonData(jsonData) {
|
|
|
const finalCourseList = [];
|
|
|
|
|
|
for (const rawCourse of rawCourseList) {
|
|
|
- // 关键字段检查: kcmc(课名), xm(教师), cdmc(教室), xqj(星期), jcs(节次范围), zcd(周次描述)
|
|
|
- if (!rawCourse.kcmc || !rawCourse.xm || !rawCourse.cdmc ||
|
|
|
- !rawCourse.xqj || !rawCourse.jcs || !rawCourse.zcd) {
|
|
|
+ // 关键字段检查:只有 kcmc(课名), xqj(星期), jcs(节次范围), zcd(周次描述) 是排课必需的。
|
|
|
+ // xm(教师) 与 cdmc(教室) 在实践课、线上课、未排地点的课程上可能为空,
|
|
|
+ // 缺这两项不影响排课,不能因此丢弃整门课程。
|
|
|
+ if (!rawCourse.kcmc || !rawCourse.xqj || !rawCourse.jcs || !rawCourse.zcd) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
@@ -92,15 +162,23 @@ function parseJsonData(jsonData) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- finalCourseList.push({
|
|
|
- name: rawCourse.kcmc.trim(),
|
|
|
- teacher: rawCourse.xm.trim(),
|
|
|
- position: rawCourse.cdmc.trim(),
|
|
|
+ const remark = buildCourseRemark(rawCourse);
|
|
|
+
|
|
|
+ const course = {
|
|
|
+ name: String(rawCourse.kcmc).trim(),
|
|
|
+ teacher: String(rawCourse.xm || "").trim(),
|
|
|
+ position: String(rawCourse.cdmc || "").trim(),
|
|
|
day: day,
|
|
|
startSection: startSection,
|
|
|
endSection: endSection,
|
|
|
weeks: weeksArray
|
|
|
- });
|
|
|
+ };
|
|
|
+
|
|
|
+ if (remark) {
|
|
|
+ course.remark = remark;
|
|
|
+ }
|
|
|
+
|
|
|
+ finalCourseList.push(course);
|
|
|
}
|
|
|
|
|
|
finalCourseList.sort((a, b) =>
|
|
|
@@ -177,7 +255,7 @@ async function whetherSummerTimeSlot() {
|
|
|
|
|
|
// } catch (error) {
|
|
|
// console.error('JS: 获取作息时间公告失败:', error);
|
|
|
- // AndroidBridge.showToast("无法获取作息时间公告,智能选择回退到预设时间。");
|
|
|
+ // window.shiguangBridge.showToast("无法获取作息时间公告,智能选择回退到预设时间。");
|
|
|
|
|
|
// // 预设日期
|
|
|
// const summerStart = new Date(new Date().getFullYear(), 3, 7); // 4月7日
|
|
|
@@ -192,10 +270,12 @@ async function whetherSummerTimeSlot() {
|
|
|
// }
|
|
|
|
|
|
// CORS 问题导致无法获取公告页,智能选择回退到预设时间。
|
|
|
+ // 冬令时是十一假期结束后调整,取 10 月 7 日;夏令时公告历年均为 4 月 7 日起执行。
|
|
|
+ // 参考教务处历年作息时间表公告:https://jwc.ujs.edu.cn/index/xl_zuo_xi_shi_jian.htm
|
|
|
|
|
|
// 预设日期
|
|
|
const summerStart = new Date(new Date().getFullYear(), 3, 7); // 4月7日
|
|
|
- const winterStart = new Date(new Date().getFullYear(), 9, 8); // 10月8日
|
|
|
+ const winterStart = new Date(new Date().getFullYear(), 9, 7); // 10月7日
|
|
|
|
|
|
const now = new Date();
|
|
|
if (now >= summerStart && now < winterStart) {
|
|
|
@@ -205,16 +285,40 @@ async function whetherSummerTimeSlot() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 计算本次导入的作息何时失效。
|
|
|
+ * 作息时间是导入时一次性写死的,不会自动跟随切令时变化,
|
|
|
+ * 所以这里返回下一个「切到另一种令时」的日期,用于提示用户届时重新导入。
|
|
|
+ * 夏令时 4 月 7 日起执行,冬令时十一假期后(10 月 7 日)起执行。
|
|
|
+ */
|
|
|
+function getNextTimeSlotSwitchDate(isSummerTime) {
|
|
|
+ const now = new Date();
|
|
|
+ const year = now.getFullYear();
|
|
|
+ const targetLabel = isSummerTime ? "冬令时" : "夏令时";
|
|
|
+
|
|
|
+ const candidates = [
|
|
|
+ { date: new Date(year, 3, 7), label: "夏令时" },
|
|
|
+ { date: new Date(year, 9, 7), label: "冬令时" },
|
|
|
+ { date: new Date(year + 1, 3, 7), label: "夏令时" },
|
|
|
+ { date: new Date(year + 1, 9, 7), label: "冬令时" }
|
|
|
+ ];
|
|
|
+
|
|
|
+ const next = candidates.find((item) => item.date > now && item.label === targetLabel);
|
|
|
+
|
|
|
+ return {
|
|
|
+ label: next.label,
|
|
|
+ text: `${next.date.getFullYear()}年${next.date.getMonth() + 1}月${next.date.getDate()}日`
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 检查是否在登录页面。
|
|
|
- * 只有当 URL 精确匹配时,才返回 true。
|
|
|
+ * 校内直连时地址是 http://jwxt.ujs.edu.cn/sso/jziotlogin,
|
|
|
+ * 校外经 WebVPN 时地址是 https://webvpn.ujs.edu.cn/http/<hex>/sso/jziotlogin,
|
|
|
+ * 因此按路径结尾判断,两种入口都能识别。
|
|
|
*/
|
|
|
function isLoginPage() {
|
|
|
- const url = window.location.href;
|
|
|
- const loginUrl = "http://jwxt.ujs.edu.cn/sso/jziotlogin";
|
|
|
-
|
|
|
- // 如果当前 URL 与指定的登录 URL 完全一致,则返回 true (是登录页)
|
|
|
- return url === loginUrl;
|
|
|
+ return window.location.pathname.endsWith("/sso/jziotlogin");
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -278,7 +382,7 @@ async function selectTimeSlot() {
|
|
|
|
|
|
async function reselectTimeSlot(selectedTimeSlot) {
|
|
|
const options = ["对的对的,就是这个", "不对不对,应该是另外一个"];
|
|
|
- const dialogTitle = "当前智能选择结果为: " + (selectedTimeSlot ? "夏令时" : "冬令时") + ",是否更改选择?";
|
|
|
+ const dialogTitle = "当前智能选择结果为: \n " + (selectedTimeSlot ? "夏令时" : "冬令时") + "\n是否更改选择?";
|
|
|
const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
dialogTitle,
|
|
|
JSON.stringify(options),
|
|
|
@@ -302,6 +406,86 @@ function getSemesterCode(semesterIndex) {
|
|
|
}
|
|
|
|
|
|
|
|
|
+/**
|
|
|
+ * 获取教务系统当前学期的起止日期。
|
|
|
+ * 首页日历区块的标题形如 "2026-2027学年1学期(2026-08-31至2027-02-21)",
|
|
|
+ * 其中起始日期就是第 1 周周一,正是 semesterStartDate 需要的值。
|
|
|
+ * 注意:该接口忽略 xnm/xqm 参数,只返回当前学期,
|
|
|
+ * 因此只有用户选择的学年学期与返回值一致时才能使用。
|
|
|
+ */
|
|
|
+async function fetchCurrentSemesterRange() {
|
|
|
+ const url = buildApiUrl("/xtgl/index_cxAreaFive.html?localeKey=zh_CN&gnmkdm=index");
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(url, {
|
|
|
+ "headers": {
|
|
|
+ "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
|
+ },
|
|
|
+ "body": "",
|
|
|
+ "method": "POST",
|
|
|
+ "credentials": "include"
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`状态码 ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const html = await response.text();
|
|
|
+ const match = html.match(/(\d{4})-\d{4}学年(\d)学期\s*[((](\d{4}-\d{2}-\d{2})至(\d{4}-\d{2}-\d{2})[))]/);
|
|
|
+
|
|
|
+ if (!match) {
|
|
|
+ console.warn("JS: 未能从日历区块解析出学期起止日期。");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const range = {
|
|
|
+ academicYear: match[1],
|
|
|
+ semesterIndex: Number(match[2]) - 1,
|
|
|
+ startDate: match[3],
|
|
|
+ endDate: match[4]
|
|
|
+ };
|
|
|
+ console.log("JS: 教务系统当前学期:", range);
|
|
|
+ return range;
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.warn("JS: 获取学期起止日期失败:", error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算课表配置。
|
|
|
+ *
|
|
|
+ * 应用侧的 saveCourseConfig 是整体覆盖而非字段级合并:没有传入的字段会被写成模型默认值,
|
|
|
+ * 其中 semesterStartDate 的默认值是 null,会把用户已经设置好的开学日期清空。
|
|
|
+ * 所以拿不到真实开学日期时返回 null,由调用方跳过整个配置保存,宁可不写也不要写坏。
|
|
|
+ *
|
|
|
+ * defaultClassDuration / defaultBreakDuration 不显式传入,会被重置为应用默认的 45 / 10 分钟,
|
|
|
+ * 与江大「45 分钟一节、课间 10 分钟」一致,因此没有副作用。
|
|
|
+ */
|
|
|
+function buildCourseConfig(courses, semesterRange, firstDayOfWeek) {
|
|
|
+ if (!semesterRange) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ let maxWeek = 0;
|
|
|
+ for (const course of courses) {
|
|
|
+ for (const week of course.weeks) {
|
|
|
+ if (week > maxWeek) {
|
|
|
+ maxWeek = week;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ semesterStartDate: semesterRange.startDate,
|
|
|
+ // 只增不减:默认 20 周,课表里出现更大的周次时才扩展。
|
|
|
+ semesterTotalWeeks: Math.max(maxWeek, 20),
|
|
|
+ firstDayOfWeek: firstDayOfWeek
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* 请求和解析课程数据
|
|
|
*/
|
|
|
@@ -312,7 +496,7 @@ async function fetchAndParseCourses(academicYear, semesterIndex) {
|
|
|
|
|
|
// API URL 和请求体
|
|
|
const xnmXqmBody = `xnm=${academicYear}&xqm=${semesterCode}&kzlx=ck&xsdm=&kclbdm=`;
|
|
|
- const url = "http://jwxt.ujs.edu.cn/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151";
|
|
|
+ const url = buildApiUrl("/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151");
|
|
|
|
|
|
console.log(`JS: 发送请求到 ${url}, body: ${xnmXqmBody}`);
|
|
|
|
|
|
@@ -353,14 +537,21 @@ async function fetchAndParseCourses(academicYear, semesterIndex) {
|
|
|
|
|
|
console.log("JS: 课程列表预览:", courses.slice(0, 5)); // 预览前5门课程
|
|
|
|
|
|
- // 默认总周数为 20,周一为一周第一天。
|
|
|
- const config = {
|
|
|
- semesterTotalWeeks: 20,
|
|
|
- firstDayOfWeek: 1
|
|
|
- };
|
|
|
+ // 集中实践课(军训、形势与政策等)没有星期和节次,无法排进周课表,单独取出用于提示。
|
|
|
+ const practiceCourses = parsePracticeCourses(jsonData);
|
|
|
+ if (practiceCourses.length > 0) {
|
|
|
+ console.log(`JS: 检测到 ${practiceCourses.length} 门集中实践课,无法自动导入。`);
|
|
|
+ }
|
|
|
|
|
|
- // 返回课程列表和配置信息
|
|
|
- return { courses: courses, config: config };
|
|
|
+ // qsxqj: 教务系统设置的一周起始星期几,缺失时按周一处理。
|
|
|
+ const rawFirstDay = Number(jsonData.qsxqj);
|
|
|
+ const firstDayOfWeek = (rawFirstDay >= 1 && rawFirstDay <= 7) ? rawFirstDay : 1;
|
|
|
+
|
|
|
+ return {
|
|
|
+ courses: courses,
|
|
|
+ practiceCourses: practiceCourses,
|
|
|
+ firstDayOfWeek: firstDayOfWeek
|
|
|
+ };
|
|
|
|
|
|
} catch (error) {
|
|
|
window.shiguangBridge.showToast(`请求或解析失败: ${error.message}`);
|
|
|
@@ -383,6 +574,48 @@ async function saveCourses(parsedCourses) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 只在能拿到真实开学日期时写入课表配置。
|
|
|
+ * 拿不到就完全不调用 saveCourseConfig —— 应用侧是整体覆盖,
|
|
|
+ * 传入不含 semesterStartDate 的配置会把用户已设置的开学日期清空。
|
|
|
+ */
|
|
|
+async function saveCourseConfigIfPossible(courses, academicYear, semesterIndex, firstDayOfWeek) {
|
|
|
+ const semesterRange = await fetchCurrentSemesterRange();
|
|
|
+
|
|
|
+ let usableRange = null;
|
|
|
+ if (semesterRange) {
|
|
|
+ const sameYear = semesterRange.academicYear === String(academicYear);
|
|
|
+ const sameSemester = semesterRange.semesterIndex === semesterIndex;
|
|
|
+
|
|
|
+ if (sameYear && sameSemester) {
|
|
|
+ usableRange = semesterRange;
|
|
|
+ } else {
|
|
|
+ console.log(
|
|
|
+ `JS: 所选学年学期(${academicYear}/第${semesterIndex + 1}学期)` +
|
|
|
+ `不是教务系统当前学期(${semesterRange.academicYear}/第${semesterRange.semesterIndex + 1}学期),跳过开学日期写入。`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const config = buildCourseConfig(courses, usableRange, firstDayOfWeek);
|
|
|
+
|
|
|
+ if (!config) {
|
|
|
+ window.shiguangBridge.showToast("未取到本学期开学日期,已跳过课表配置,请在应用内手动设置开学日期。");
|
|
|
+ console.log("JS: 无可用开学日期,跳过 saveCourseConfig 以保留用户现有配置。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
|
|
|
+ window.shiguangBridge.showToast(
|
|
|
+ `课表配置更新成功!开学日期 ${config.semesterStartDate},总周数 ${config.semesterTotalWeeks} 周。`
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ window.shiguangBridge.showToast(`课表配置保存失败: ${error.message}`);
|
|
|
+ console.error('JS: Save Config Error:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 上午作息时间
|
|
|
// 北固及本部主楼、主A楼、生环楼、汽车能动楼、京江楼
|
|
|
const AMorningTimeSlots = [
|
|
|
@@ -488,7 +721,9 @@ function getCampusTypeFromPosition(position) {
|
|
|
function getMorningTypeFromPosition(position) {
|
|
|
const text = String(position || "").trim();
|
|
|
|
|
|
- if (text.includes("主A楼") || text.includes("京江楼")) return "A";
|
|
|
+ // 教务系统返回的是「京江2号楼2101」「京江3号楼3407」这类名称,没有「京江楼」这个写法,
|
|
|
+ // 所以这里匹配「京江」而不是「京江楼」。
|
|
|
+ if (text.includes("主A楼") || text.includes("京江")) return "A";
|
|
|
if (text.includes("三江楼")) return "B";
|
|
|
if (text.includes("三山楼") || text.includes("讲堂群")) return "C";
|
|
|
|
|
|
@@ -642,33 +877,51 @@ async function runImportFlow() {
|
|
|
console.log("JS: 课程获取或解析失败,流程终止。");
|
|
|
return;
|
|
|
}
|
|
|
- const { courses, config } = result;
|
|
|
+ const { courses, practiceCourses, firstDayOfWeek } = result;
|
|
|
|
|
|
const coursesWithCustomTime = applyCustomTimeToCourses(courses, isSummerTime);
|
|
|
|
|
|
- // 只匹配了主A楼、京江楼、三江楼、三山楼、讲堂群这几个教学楼的作息时间,其他课程都使用默认时间
|
|
|
- const timeSlotAlert = await window.shiguangBridgePromise.showAlert(
|
|
|
- "楼栋自定义时间提示",
|
|
|
- "脚本已根据课程所在位置智能匹配了作息时间,部分课程可能与预设时间不符。\n" +
|
|
|
- "请在课表页面核对课程时间,如有错误请手动修改课程所在位置或节次信息。\n" +
|
|
|
- "(目前仅主A楼、京江楼、三江楼、三山楼、讲堂群的课程会应用自定义时间,其他课程默认时间不变)\n" +
|
|
|
+ // 作息时间在导入时一次性写入,不会自动跟随切令时变化,需要明确告知用户。
|
|
|
+ // 同时说明只有部分教学楼收录了独立作息,其余楼栋使用默认作息时间。
|
|
|
+ const nextSwitch = getNextTimeSlotSwitchDate(isSummerTime);
|
|
|
+ await window.shiguangBridgePromise.showAlert(
|
|
|
+ "作息时间提示",
|
|
|
+ `本次按${isSummerTime ? "夏令时" : "冬令时"}导入。作息时间在导入时写入,不会自动跟随学校切换令时。\n` +
|
|
|
+ `${nextSwitch.text}起学校切换为${nextSwitch.label},届时请重新导入课表,或在应用内手动修改时间段。\n\n` +
|
|
|
+ "脚本已根据课程所在位置匹配作息时间,部分课程可能与预设时间不符。\n" +
|
|
|
+ "请在课表页面核对课程时间,如有错误请手动修改课程所在位置或节次信息。\n\n" +
|
|
|
+ "已收录独立作息的楼栋:主A楼、京江各号楼、三江楼、三山楼、讲堂群。\n" +
|
|
|
+ "其余楼栋(各学院楼、各实验室、运动场、未排地点等)使用默认作息时间。\n\n" +
|
|
|
"欢迎其他楼栋的同学提供课程时间信息以完善脚本!",
|
|
|
"我知道了"
|
|
|
);
|
|
|
|
|
|
+ if (practiceCourses.length > 0) {
|
|
|
+ const practiceList = practiceCourses
|
|
|
+ .map((item) => {
|
|
|
+ const teacher = item.teacher ? `(${item.teacher})` : "";
|
|
|
+ const weekDesc = item.weekDesc ? ` ${item.weekDesc}` : "";
|
|
|
+ return `· ${item.name}${teacher}${weekDesc}`;
|
|
|
+ })
|
|
|
+ .join("\n");
|
|
|
+
|
|
|
+ console.log("JS: 集中实践课列表:", practiceCourses);
|
|
|
+ await window.shiguangBridgePromise.showAlert(
|
|
|
+ "集中实践课需手动添加",
|
|
|
+ `本学期有 ${practiceCourses.length} 门集中实践课,教务系统未给出星期和节次,无法自动导入:\n\n` +
|
|
|
+ practiceList +
|
|
|
+ "\n\n请按实际安排在应用内手动添加。",
|
|
|
+ "我知道了"
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
const saveResult = await saveCourses(coursesWithCustomTime);
|
|
|
if (!saveResult) {
|
|
|
console.log("JS: 课程保存失败,流程终止。");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
|
|
|
- window.shiguangBridge.showToast(`课表配置更新成功!总周数:${config.semesterTotalWeeks}周。`);
|
|
|
- } catch (error) {
|
|
|
- window.shiguangBridge.showToast(`课表配置保存失败: ${error.message}`);
|
|
|
- console.error('JS: Save Config Error:', error);
|
|
|
- }
|
|
|
+ await saveCourseConfigIfPossible(coursesWithCustomTime, academicYear, semesterIndex, firstDayOfWeek);
|
|
|
|
|
|
await importPresetTimeSlots(isSummerTime ? SummerTimeSlots : WinterTimeSlots);
|
|
|
|
|
|
@@ -678,4 +931,4 @@ async function runImportFlow() {
|
|
|
window.shiguangBridge.notifyTaskCompletion();
|
|
|
}
|
|
|
|
|
|
-runImportFlow();
|
|
|
+runImportFlow();
|