|
@@ -0,0 +1,189 @@
|
|
|
|
|
+// 大连工程学院教务系统(jw.dlutci.edu.cn)拾光课表导入适配脚本
|
|
|
|
|
+// 基于智慧教务(app)系统,接口为 xskcb.do 获取课表、xnxqcx.do 选择学期
|
|
|
|
|
+
|
|
|
|
|
+// 周次位图解析:SKZC 为"0"/"1"串,第 i 位为"1"表示第 i+1 周有课
|
|
|
|
|
+function parseWeeks(skzc) {
|
|
|
|
|
+ const value = String(skzc || "");
|
|
|
|
|
+ const weeks = [];
|
|
|
|
|
+ for (let i = 0; i < value.length; i++) {
|
|
|
|
|
+ if (value[i] === "1") weeks.push(i + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return weeks;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getErrorMessage(error) {
|
|
|
|
|
+ if (error && typeof error.message === "string" && error.message.trim()) return error.message;
|
|
|
|
|
+ if (typeof error === "string" && error.trim()) return error;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const serialized = JSON.stringify(error);
|
|
|
|
|
+ if (serialized && serialized !== "{}") return serialized;
|
|
|
|
|
+ } catch (_) {
|
|
|
|
|
+ // Ignore serialization failures.
|
|
|
|
|
+ }
|
|
|
|
|
+ return "未知错误";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 教室:JASMC 为主,XXXQDM_DISPLAY 校区做后缀;均无则"待定"
|
|
|
|
|
+function parsePosition(row) {
|
|
|
|
|
+ const campus = String(row.XXXQDM_DISPLAY || "").trim();
|
|
|
|
|
+ const room = String(row.JASMC || "").trim();
|
|
|
|
|
+ if (campus && room) return `${room}(${campus})`;
|
|
|
|
|
+ return room || campus || "待定";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function parseCourse(row) {
|
|
|
|
|
+ const day = Number(row.SKXQ);
|
|
|
|
|
+ const startSection = Number(row.KSJC);
|
|
|
|
|
+ const endSection = Number(row.JSJC);
|
|
|
|
|
+ const weeks = parseWeeks(row.SKZC);
|
|
|
|
|
+ if (!row.KCM || !day || day < 1 || day > 7 || !startSection || !endSection ||
|
|
|
|
|
+ startSection > endSection || weeks.length === 0) return null;
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: String(row.KCM).trim(),
|
|
|
|
|
+ teacher: String(row.SKJS || "").split(/[\\/、,,]/)[0].trim() || "未知",
|
|
|
|
|
+ position: parsePosition(row),
|
|
|
|
|
+ day,
|
|
|
|
|
+ startSection,
|
|
|
|
|
+ endSection,
|
|
|
|
|
+ weeks
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 接口返回 JSON = { datas: { xskcb: { rows: [...] } } },按行为一门课;同一门课多处时段会分多条,按组合键去重
|
|
|
|
|
+function transformSchedule(payload) {
|
|
|
|
|
+ const rows = payload && payload.datas && payload.datas.xskcb && payload.datas.xskcb.rows;
|
|
|
|
|
+ if (!Array.isArray(rows)) return [];
|
|
|
|
|
+ const rawCourses = rows.map(parseCourse).filter(Boolean);
|
|
|
|
|
+
|
|
|
|
|
+ const seen = new Set();
|
|
|
|
|
+ const courses = [];
|
|
|
|
|
+ for (const c of rawCourses) {
|
|
|
|
|
+ const key = `${c.name}|${c.day}|${c.startSection}|${c.endSection}|${c.weeks.join(',')}|${c.teacher}|${c.position}`;
|
|
|
|
|
+ if (seen.has(key)) continue;
|
|
|
|
|
+ seen.add(key);
|
|
|
|
|
+ courses.push(c);
|
|
|
|
|
+ }
|
|
|
|
|
+ return courses;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断是否登录页
|
|
|
|
|
+function isLoginPage() {
|
|
|
|
|
+ const url = window.location.href;
|
|
|
|
|
+ return /login/i.test(url);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function fetchCurrentSemester() {
|
|
|
|
|
+ const response = await fetch("/jwapp/sys/wdkb/modules/jshkcb/dqxnxq.do", {
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
|
+ "X-Requested-With": "XMLHttpRequest"
|
|
|
|
|
+ },
|
|
|
|
|
+ body: "",
|
|
|
|
|
+ credentials: "include"
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) throw new Error(`当前学期接口请求失败(HTTP ${response.status})`);
|
|
|
|
|
+ const payload = await response.json();
|
|
|
|
|
+ const rows = payload && payload.datas && payload.datas.dqxnxq && payload.datas.dqxnxq.rows;
|
|
|
|
|
+ const term = Array.isArray(rows) && rows.length > 0 ? rows[0] : null;
|
|
|
|
|
+ if (!term || (!term.DM && !term.XNXQDM)) throw new Error("未获取到当前学期信息。");
|
|
|
|
|
+ return { code: String(term.XNXQDM || term.DM) };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function fetchSemesterList() {
|
|
|
|
|
+ const response = await fetch("/jwapp/sys/wdkb/modules/jshkcb/xnxqcx.do", {
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
|
+ "X-Requested-With": "XMLHttpRequest"
|
|
|
|
|
+ },
|
|
|
|
|
+ body: "*order=-DM",
|
|
|
|
|
+ credentials: "include"
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) throw new Error(`学期列表接口请求失败(HTTP ${response.status})`);
|
|
|
|
|
+ const payload = await response.json();
|
|
|
|
|
+ const rows = payload && payload.datas && payload.datas.xnxqcx && payload.datas.xnxqcx.rows;
|
|
|
|
|
+ if (!Array.isArray(rows)) throw new Error("学期列表返回数据格式异常。");
|
|
|
|
|
+ return rows.filter(item => item && (item.DM || item.XNXQDM)).map(item => {
|
|
|
|
|
+ const code = String(item.XNXQDM || item.DM);
|
|
|
|
|
+ const MC = String(item.MC || item.DM || item.XNXQDM);
|
|
|
|
|
+ return { code, name: MC };
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 选择学期,默认定位到当前学期
|
|
|
|
|
+async function selectSemester() {
|
|
|
|
|
+ const current = await fetchCurrentSemester();
|
|
|
|
|
+ const list = await fetchSemesterList();
|
|
|
|
|
+ if (list.length === 0) throw new Error("未获取到学期列表。");
|
|
|
|
|
+
|
|
|
|
|
+ const defaultIndex = Math.max(0, list.findIndex(s => s.code === current.code));
|
|
|
|
|
+ const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
|
|
+ "请选择学期",
|
|
|
|
|
+ JSON.stringify(list.map(s => s.name)),
|
|
|
|
|
+ defaultIndex
|
|
|
|
|
+ );
|
|
|
|
|
+ if (selectedIndex === null || selectedIndex === undefined) return null;
|
|
|
|
|
+ return list[selectedIndex];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function fetchCourses(xnxqdm) {
|
|
|
|
|
+ window.shiguangBridge.showToast("正在请求课表数据...");
|
|
|
|
|
+ // 请求参数仅传 XNXQDM,即可返回该学期全部课表
|
|
|
|
|
+ const response = await fetch("/jwapp/sys/wdkb/modules/xskcb/xskcb.do", {
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
|
+ "X-Requested-With": "XMLHttpRequest"
|
|
|
|
|
+ },
|
|
|
|
|
+ body: `XNXQDM=${encodeURIComponent(xnxqdm)}`,
|
|
|
|
|
+ credentials: "include"
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) throw new Error(`课表接口请求失败(HTTP ${response.status})`);
|
|
|
|
|
+ const payload = await response.json();
|
|
|
|
|
+ return transformSchedule(payload);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function runImportFlow() {
|
|
|
|
|
+ if (isLoginPage()) {
|
|
|
|
|
+ window.shiguangBridge.showToast("导入失败:请先登录教务系统!");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const alertConfirmed = await window.shiguangBridgePromise.showAlert(
|
|
|
|
|
+ "教务系统课表导入",
|
|
|
|
|
+ "导入前请确保您已登录教务系统,建议在课表页面进行导入",
|
|
|
|
|
+ "好的,开始导入"
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!alertConfirmed) {
|
|
|
|
|
+ window.shiguangBridge.showToast("用户取消了导入。");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const semester = await selectSemester();
|
|
|
|
|
+ if (!semester) {
|
|
|
|
|
+ window.shiguangBridge.showToast("用户取消了导入。");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log(`JS: 选择学期: ${semester.name} (${semester.code})`);
|
|
|
|
|
+
|
|
|
|
|
+ const courses = await fetchCourses(semester.code);
|
|
|
|
|
+ if (courses.length === 0) {
|
|
|
|
|
+ window.shiguangBridge.showToast("未找到任何课程数据,请检查所选学期是否正确。");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ window.shiguangBridge.showToast(`正在保存 ${courses.length} 门课程...`);
|
|
|
|
|
+ await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses, null, 2));
|
|
|
|
|
+
|
|
|
|
|
+ window.shiguangBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
|
|
|
|
|
+ window.shiguangBridge.notifyTaskCompletion();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.shiguangBridge.showToast(`导入失败:${getErrorMessage(error)}`);
|
|
|
|
|
+ console.error("JS: Import Error:", error);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+runImportFlow();
|