|
|
@@ -0,0 +1,276 @@
|
|
|
+// 山西电子科技学院(sxdzkj.edu.cn) 拾光课程表适配脚本
|
|
|
+// 基于青果教务系统适配
|
|
|
+// 适配器ID: SXDZKJ
|
|
|
+
|
|
|
+async function checkLoginEnvironment() {
|
|
|
+ const currentUrl = window.location.href;
|
|
|
+ if (currentUrl.includes("sso.sxdzkj.edu.cn/sso-auth") || currentUrl.includes("cas/login")) {
|
|
|
+ window.shiguangBridge.showToast("请先登录教务系统再进行导入");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+function parseWeeks(weekStr) {
|
|
|
+ const weeks = [];
|
|
|
+ const groups = weekStr.split(',');
|
|
|
+ groups.forEach(group => {
|
|
|
+ const isSingle = group.includes('单');
|
|
|
+ const isDouble = group.includes('双');
|
|
|
+ const rangeMatch = group.match(/(\d+)-(\d+)/);
|
|
|
+
|
|
|
+ if (rangeMatch) {
|
|
|
+ const start = parseInt(rangeMatch[1]);
|
|
|
+ const end = parseInt(rangeMatch[2]);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ if (isSingle && i % 2 === 0) continue;
|
|
|
+ if (isDouble && i % 2 !== 0) continue;
|
|
|
+ weeks.push(i);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const num = parseInt(group.replace(/[^\d]/g, ''));
|
|
|
+ if (!isNaN(num)) weeks.push(num);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return Array.from(new Set(weeks)).sort((a, b) => a - b);
|
|
|
+}
|
|
|
+
|
|
|
+function encodeParams(params) {
|
|
|
+ return btoa(params);
|
|
|
+}
|
|
|
+
|
|
|
+const dayMap = {
|
|
|
+ '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '日': 7, '天': 7
|
|
|
+};
|
|
|
+
|
|
|
+function parseScheduleString(scheduleStr, teacher, courseName) {
|
|
|
+ const results = [];
|
|
|
+ if (!scheduleStr || scheduleStr.trim() === '') return results;
|
|
|
+
|
|
|
+ const sessions = scheduleStr.split(';').map(s => s.trim()).filter(s => s);
|
|
|
+
|
|
|
+ sessions.forEach(session => {
|
|
|
+ // 有地点: "1-16周 三[7-8] 南-317(115)" / "1-16周(单) 一[5-6] 南-311(115)"
|
|
|
+ // 无地点: "1-16周 三[5-6]"
|
|
|
+ const match = session.match(/([\d\-]+周(?:\([单双]\))?)\s+([一二三四五六日天])\[(\d+\-\d+)\](?:\s*(.*))?/);
|
|
|
+ if (match) {
|
|
|
+ const weekStr = match[1];
|
|
|
+ const dayChar = match[2];
|
|
|
+ const sectionStr = match[3];
|
|
|
+ const position = (match[4] || "").trim();
|
|
|
+
|
|
|
+ const weeks = parseWeeks(weekStr);
|
|
|
+ const day = dayMap[dayChar];
|
|
|
+ const sections = sectionStr.split('-').map(Number);
|
|
|
+
|
|
|
+ if (weeks.length > 0 && day && sections.length === 2) {
|
|
|
+ results.push({
|
|
|
+ name: courseName,
|
|
|
+ teacher: teacher,
|
|
|
+ position: position,
|
|
|
+ day: day,
|
|
|
+ startSection: sections[0],
|
|
|
+ endSection: sections[1],
|
|
|
+ weeks: weeks
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return results;
|
|
|
+}
|
|
|
+
|
|
|
+function parseCourseData(htmlText) {
|
|
|
+ const parser = new DOMParser();
|
|
|
+ const doc = parser.parseFromString(htmlText, 'text/html');
|
|
|
+ const allCourses = [];
|
|
|
+
|
|
|
+ const allTables = doc.querySelectorAll('table');
|
|
|
+ if (allTables.length === 0) {
|
|
|
+ console.warn("未找到表格");
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 课程列表(含上课时间地点列)
|
|
|
+ allTables.forEach(table => {
|
|
|
+ if (!/上课时间地点/.test(table.textContent)) return;
|
|
|
+ allCourses.push(...parseCourseList(table));
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("解析完成,共课程条目:", allCourses.length);
|
|
|
+ return allCourses;
|
|
|
+}
|
|
|
+
|
|
|
+function parseCourseList(table) {
|
|
|
+ const courses = [];
|
|
|
+ const rows = table.querySelectorAll('tr');
|
|
|
+ rows.forEach(row => {
|
|
|
+ const cells = row.querySelectorAll('td');
|
|
|
+ // 课程列表:第3列课程、第7列教师、第11列时间地点
|
|
|
+ if (cells.length < 11) return;
|
|
|
+ const courseCell = cells[2];
|
|
|
+ const teacherCell = cells[6];
|
|
|
+ const scheduleCell = cells[10];
|
|
|
+ const courseName = courseCell.textContent.trim().replace(/\[.*?\]/g, '');
|
|
|
+ const teacher = teacherCell.textContent.trim().replace(/\[.*?\]/g, '');
|
|
|
+ const scheduleStr = scheduleCell.textContent.trim();
|
|
|
+ if (!scheduleStr) return;
|
|
|
+ const parsed = parseScheduleString(scheduleStr, teacher, courseName);
|
|
|
+ courses.push(...parsed);
|
|
|
+ });
|
|
|
+ return courses;
|
|
|
+}
|
|
|
+
|
|
|
+async function getYearAndSemester() {
|
|
|
+ const currentYear = new Date().getFullYear();
|
|
|
+ const yearStr = await window.shiguangBridgePromise.showPrompt(
|
|
|
+ "输入学年",
|
|
|
+ "请输入学年(如 2025-2026 输入2025):",
|
|
|
+ currentYear.toString(),
|
|
|
+ "validateYearInput"
|
|
|
+ );
|
|
|
+
|
|
|
+ if (yearStr === null) return null;
|
|
|
+
|
|
|
+ const semesterIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
+ "选择学期",
|
|
|
+ JSON.stringify(["第一学期", "第二学期"]),
|
|
|
+ 0
|
|
|
+ );
|
|
|
+
|
|
|
+ if (semesterIndex === null) return null;
|
|
|
+
|
|
|
+ return {
|
|
|
+ xn: yearStr,
|
|
|
+ xq: semesterIndex === 0 ? "0" : "1"
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function validateYearInput(input) {
|
|
|
+ if (/^[0-9]{4}$/.test(input)) {
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ return "请输入四位数字的学年!";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchCourses(xn, xq) {
|
|
|
+ try {
|
|
|
+ window.shiguangBridge.showToast("正在获取课表数据...");
|
|
|
+
|
|
|
+ let xh = "";
|
|
|
+ try {
|
|
|
+ const userCodeMatch = document.cookie.match(/userCode[=:]([^;]+)/);
|
|
|
+ if (userCodeMatch) xh = userCodeMatch[1];
|
|
|
+ } catch (e) {}
|
|
|
+
|
|
|
+ if (!xh) {
|
|
|
+ xh = await window.shiguangBridgePromise.showPrompt(
|
|
|
+ "输入学号",
|
|
|
+ "请输入你的学号:",
|
|
|
+ "",
|
|
|
+ null
|
|
|
+ );
|
|
|
+ if (!xh) return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("使用参数:", { xn, xq, xh });
|
|
|
+
|
|
|
+ const paramStr = `xn=${xn}&xq=${xq}&xh=${xh}`;
|
|
|
+ const encodedParams = encodeParams(paramStr);
|
|
|
+
|
|
|
+ const url = `https://jwxt.sxdzkj.edu.cn/wsxk/xkjg.ckdgxsxdkchj_data10319.jsp?params=${encodedParams}`;
|
|
|
+
|
|
|
+ console.log("请求课表数据:", url);
|
|
|
+
|
|
|
+ const response = await fetch(url, {
|
|
|
+ method: "GET",
|
|
|
+ credentials: "include"
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`请求失败: ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const arrayBuffer = await response.arrayBuffer();
|
|
|
+ let text;
|
|
|
+ try {
|
|
|
+ text = new TextDecoder('gbk').decode(arrayBuffer);
|
|
|
+ } catch (e) {
|
|
|
+ text = new TextDecoder('utf-8').decode(arrayBuffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("课表响应长度:", text.length);
|
|
|
+
|
|
|
+ return parseCourseData(text);
|
|
|
+ } catch (error) {
|
|
|
+ window.shiguangBridge.showToast("获取课表失败: " + error.message);
|
|
|
+ console.error("获取课表失败:", error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function importPresetTimeSlots() {
|
|
|
+ const slots = [
|
|
|
+ { "number": 1, "startTime": "08:00", "endTime": "08:50" },
|
|
|
+ { "number": 2, "startTime": "09:00", "endTime": "09:50" },
|
|
|
+ { "number": 3, "startTime": "10:10", "endTime": "11:00" },
|
|
|
+ { "number": 4, "startTime": "11:10", "endTime": "12:00" },
|
|
|
+ { "number": 5, "startTime": "14:00", "endTime": "14:50" },
|
|
|
+ { "number": 6, "startTime": "15:00", "endTime": "15:50" },
|
|
|
+ { "number": 7, "startTime": "16:10", "endTime": "17:00" },
|
|
|
+ { "number": 8, "startTime": "17:10", "endTime": "18:00" },
|
|
|
+ { "number": 9, "startTime": "19:00", "endTime": "19:50" },
|
|
|
+ { "number": 10, "startTime": "20:00", "endTime": "20:50" }
|
|
|
+ ];
|
|
|
+
|
|
|
+ try {
|
|
|
+ await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(slots));
|
|
|
+ window.shiguangBridge.showToast("时间段导入成功");
|
|
|
+ } catch (error) {
|
|
|
+ console.error("导入时间段失败:", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function runImportFlow() {
|
|
|
+ const isReady = await checkLoginEnvironment();
|
|
|
+ if (!isReady) return;
|
|
|
+
|
|
|
+ const confirmed = await window.shiguangBridgePromise.showAlert(
|
|
|
+ "教务导入",
|
|
|
+ "山西电子科技学院课表导入\n\n请确保已登录教务系统。\n点击确定开始导入。",
|
|
|
+ "确定导入"
|
|
|
+ );
|
|
|
+ if (!confirmed) return;
|
|
|
+
|
|
|
+ const semesterParams = await getYearAndSemester();
|
|
|
+ if (!semesterParams) {
|
|
|
+ window.shiguangBridge.showToast("导入已取消");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("选择的学期:", semesterParams);
|
|
|
+
|
|
|
+ const courses = await fetchCourses(semesterParams.xn, semesterParams.xq);
|
|
|
+ if (!courses || courses.length === 0) {
|
|
|
+ window.shiguangBridge.showToast("未找到课程数据");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("解析到的课程:", courses);
|
|
|
+
|
|
|
+ try {
|
|
|
+ await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
+ window.shiguangBridge.showToast(`成功保存 ${courses.length} 门课程`);
|
|
|
+ } catch (error) {
|
|
|
+ window.shiguangBridge.showToast("保存课程失败: " + error.message);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await importPresetTimeSlots();
|
|
|
+
|
|
|
+ window.shiguangBridge.showToast(`导入完成!共 ${courses.length} 条课程记录`);
|
|
|
+ window.shiguangBridge.notifyTaskCompletion();
|
|
|
+}
|
|
|
+
|
|
|
+runImportFlow();
|