| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- // 景德镇艺术职业大学 (jdzvua.edu.cn) 拾光课程表适配脚本
- // 纯 HTML 抓取:课表在 iframe 内 #manualArrangeCourseTable,切换学期无网络请求,
- // 请先在页面上切换到目标学期课表再执行导入
- // 课程信息在 td.infoTitle 的 title 属性:`课程名(编码) (教师);(周次,地点;...` 成对出现,
- // td id 形如 TD{星期0-6}{节次-1}_0,rowspan 为跨节数
- // 作息按楼栋分两组(08:00 起 / 08:20 起),课程覆盖多的一组写入预设作息,
- // 少的一组课程按其楼栋时间写入自定义时间
- const TIME_SLOT_A = [
- { number: 1, startTime: "08:20", endTime: "09:05" },
- { number: 2, startTime: "09:15", endTime: "10:00" },
- { number: 3, startTime: "10:35", endTime: "11:20" },
- { number: 4, startTime: "11:30", endTime: "12:15" },
- { number: 5, startTime: "14:20", endTime: "15:05" },
- { number: 6, startTime: "15:15", endTime: "16:00" },
- { number: 7, startTime: "16:35", endTime: "17:20" },
- { number: 8, startTime: "17:30", endTime: "18:15" },
- { number: 9, startTime: "19:20", endTime: "20:05" },
- { number: 10, startTime: "20:15", endTime: "21:00" }
- ];
- const TIME_SLOT_B = [
- { number: 1, startTime: "08:00", endTime: "08:45" },
- { number: 2, startTime: "08:55", endTime: "09:40" },
- { number: 3, startTime: "10:25", endTime: "11:10" },
- { number: 4, startTime: "11:20", endTime: "12:05" },
- { number: 5, startTime: "14:00", endTime: "14:45" },
- { number: 6, startTime: "14:55", endTime: "15:40" },
- { number: 7, startTime: "16:25", endTime: "17:10" },
- { number: 8, startTime: "17:20", endTime: "18:05" },
- { number: 9, startTime: "19:20", endTime: "20:05" },
- { number: 10, startTime: "20:15", endTime: "21:00" }
- ];
- // 08:20 起的楼栋组
- const BUILDINGS_A = ["上茶苑教6栋", "上茶苑教8栋", "芭蕉苑教14栋", "芭蕉苑教16栋",
- "芭蕉苑教18栋", "芭蕉苑教20栋", "长芗苑教10栋", "长芗苑教12栋", "香樟苑教2栋", "香樟苑教4栋"];
- // 08:00 起的楼栋组
- const BUILDINGS_B = ["上茶苑教7栋", "上茶苑教9栋", "体育馆", "梧桐苑", "芭蕉苑教15栋",
- "芭蕉苑教17栋", "芭蕉苑教19栋", "芭蕉苑教21栋", "长芗苑教11栋", "长芗苑教13栋",
- "香樟苑教1栋", "香樟苑教3栋", "香樟苑教5栋"];
- // 周次:"16-17" / "2-6 9-12" / "18",空格分隔多段
- function parseWeeks(text) {
- const weeks = [];
- for (const part of String(text).trim().split(/\s+/)) {
- const range = part.match(/^(\d+)-(\d+)$/);
- if (range) {
- for (let w = Number(range[1]); w <= Number(range[2]); w++) weeks.push(w);
- } else if (/^\d+$/.test(part)) {
- weeks.push(Number(part));
- }
- }
- return [...new Set(weeks)].sort((a, b) => a - b);
- }
- // 地点末尾的校区标记 (景艺) 在 title 里常缺右括号或多一个右括号,按实际形态归一
- function normalizeRoom(room) {
- const text = String(room).trim();
- if (/\(景艺$/.test(text)) return text + ")";
- if (/\(景艺\)\)$/.test(text)) return text.slice(0, -1);
- return text;
- }
- // 解析单个课表格子,title 按分号切成「课程/周次地点」交替的 token
- function parseCell(id, rowspan, title) {
- const matched = String(id).match(/^TD(\d+)_/);
- if (!matched) return [];
- const n = Number(matched[1]);
- const day = Math.floor(n / 10) + 1;
- const startSection = (n % 10) + 1;
- const endSection = startSection + (Number(rowspan) || 1) - 1;
- if (day > 7 || endSection > 10) return [];
- const courses = [];
- const tokens = String(title).split(";").map(s => s.trim()).filter(Boolean);
- for (let i = 0; i < tokens.length; i++) {
- if (tokens[i].charAt(0) === "(") continue;
- const info = tokens[i + 1];
- if (!info || info.charAt(0) !== "(") continue;
- i++;
- const body = info.slice(1);
- const comma = body.indexOf(",");
- if (comma < 0) continue;
- const weeks = parseWeeks(body.slice(0, comma));
- if (weeks.length === 0) continue;
- const head = tokens[i - 1].match(/^(.+?)\(([^)]+)\)\s*\(([^)]*)\)/);
- courses.push({
- name: head ? head[1] : tokens[i - 1],
- teacher: head ? head[3] : "",
- position: normalizeRoom(body.slice(comma + 1)),
- day: day,
- startSection: startSection,
- endSection: endSection,
- weeks: weeks
- });
- }
- return courses;
- }
- // 在当前文档与各级 frame 中查找课表
- function findCourseTable(doc) {
- const table = doc.querySelector("#manualArrangeCourseTable");
- if (table) return table;
- for (const frame of doc.querySelectorAll("iframe,frame")) {
- try {
- const found = findCourseTable(frame.contentDocument);
- if (found) return found;
- } catch (e) {
- // 跨域 frame 取不到 contentDocument,跳过
- }
- }
- return null;
- }
- // 楼栋匹配作息组,未匹配(如实践场地)返回 null 用预设作息
- function groupOfPosition(position) {
- for (const building of BUILDINGS_A) if (position.includes(building)) return TIME_SLOT_A;
- for (const building of BUILDINGS_B) if (position.includes(building)) return TIME_SLOT_B;
- return null;
- }
- // 按所在作息组写入自定义时间(两组作息仅少数节次不同)
- function applyCustomTime(course, group) {
- const start = group.find(s => s.number === course.startSection);
- const end = group.find(s => s.number === course.endSection);
- if (!start || !end) return course;
- return { ...course, isCustomTime: true, customStartTime: start.startTime, customEndTime: end.endTime };
- }
- async function runImportFlow() {
- const confirmed = await window.shiguangBridgePromise.showAlert(
- "教务系统课表导入",
- "导入前请先在页面上切换到要导入的学期课表,再点击导入",
- "好的,开始导入");
- if (!confirmed) { window.shiguangBridge.showToast("用户取消了导入。"); return; }
- const table = findCourseTable(document);
- if (!table) {
- window.shiguangBridge.showToast("导入失败:未找到课表,请确认已打开课表页面。");
- return;
- }
- const courses = [];
- for (const td of table.querySelectorAll('td[id^="TD"]')) {
- const title = td.getAttribute("title");
- if (!title) continue;
- courses.push(...parseCell(td.id, td.getAttribute("rowspan"), title));
- }
- if (courses.length === 0) {
- window.shiguangBridge.showToast("未解析到课程,请确认课表已加载。");
- return;
- }
- // 统计两组楼栋各覆盖多少课程:覆盖多的一组直接写入预设作息,
- // 少的一组课程按其楼栋时间写入自定义时间,数量相同时默认 08:00 组
- let countA = 0;
- let countB = 0;
- for (const course of courses) {
- const group = groupOfPosition(course.position);
- if (group === TIME_SLOT_A) countA++;
- else if (group === TIME_SLOT_B) countB++;
- }
- const majority = countA > countB ? TIME_SLOT_A : TIME_SLOT_B;
- const result = courses.map(course => {
- const group = groupOfPosition(course.position);
- return group && group !== majority ? applyCustomTime(course, group) : course;
- });
- result.sort((a, b) => a.day - b.day || a.startSection - b.startSection || a.name.localeCompare(b.name));
- try {
- await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(result));
- } catch (e) {
- window.shiguangBridge.showToast("课程保存失败: " + e.message);
- return;
- }
- try { await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(majority)); } catch (e) {}
- window.shiguangBridge.showToast("课程导入成功,共导入 " + result.length + " 门课程!");
- window.shiguangBridge.notifyTaskCompletion();
- }
- runImportFlow();
|