| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- // 沈阳科技学院 (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) {
- // 判断是否为实验课程(存在 SYXMMC 字段)
- const isExperiment = rawCourse.SYXMMC !== undefined && rawCourse.SYXMMC !== null;
-
- // 课程名称:实验课程使用 "KCM - SYXMMC" 格式
- const courseName = isExperiment
- ? `${rawCourse.KCM} - ${rawCourse.SYXMMC}`
- : rawCourse.KCM;
-
- const teacherName = rawCourse.SKJS ? rawCourse.SKJS.split('/')[0] : (rawCourse.JSM || '');
- 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;
- }
- // 获取当前学期作为默认值
- let defaultDM = null;
- try {
- const dqResponse = await fetch("http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/jshkcb/dqxnxq.do", {
- headers,
- method: "POST",
- credentials: "include"
- });
- const dqData = await dqResponse.json();
- const currentSemester = dqData?.datas?.dqxnxq?.rows?.[0];
- if (currentSemester) {
- defaultDM = currentSemester.DM;
- }
- } catch (e) {
- console.warn("获取当前学期失败,将不使用默认值:", e);
- }
- const topSemesters = semesterList.slice(0, 10);
- const displayNames = topSemesters.map(item => item.MC || item.DM);
- // 查找默认学期的索引
- let defaultIndex = -1;
- if (defaultDM) {
- defaultIndex = topSemesters.findIndex(item => item.DM === defaultDM);
- }
- const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
- "请选择学期",
- JSON.stringify(displayNames),
- defaultIndex
- );
- 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 || [];
- // 获取实验课程
- let rawExperimentCourses = [];
- try {
- const expUrl = "http://jwxt.syist.edu.cn:30334/jwapp/sys/wdkb/modules/syjxkcb/cxsyjxxskb.do";
- const expBody = `XNXQDM=${XNXQDM}`;
- const expResponse = await fetch(expUrl, { headers, body: expBody, method: "POST", credentials: "include" });
- const expData = await expResponse.json();
- rawExperimentCourses = expData?.datas?.cxsyjxxskb?.rows || [];
- } catch (e) {
- console.warn("获取实验课程失败,将仅使用理论课程:", e);
- }
- // 合并理论课程和实验课程
- const allRawCourses = [...rawCourses, ...rawExperimentCourses];
- if (allRawCourses.length === 0) {
- window.shiguangBridge.showToast("该学期未查询到您的课程数据。");
- return null;
- }
- // 解析所有课程
- let parsedCourses = allRawCourses.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);
- }
- // 清理临时字段
- const cleanList = cleanCourses(parsedCourses);
- // 执行两阶段合并(节次合并 + 周次合并)
- const finalCourses = mergeAndDistinctCourses(cleanList);
- const courseConfig = await fetchSemesterConfig(semesterObj.XNDM, semesterObj.XQDM);
- // 显示实验课程数量提示
- if (rawExperimentCourses.length > 0) {
- window.shiguangBridge.showToast(`共获取到 ${rawCourses.length} 门理论课程和 ${rawExperimentCourses.length} 门实验课程。`);
- }
- 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();
|