| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- // 通用 URP 教务拾光课程表适配脚本
- /**
- * 解析复杂的周次文本
- * 示例: "1-8,10-17周" -> [1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17]
- * 示例: "1-5,7-8,10-12周双周,13-17周" -> 自动过滤单双周
- */
- function parseWeekText(weekStr) {
- let weeks = new Set();
- if (!weekStr) return [];
-
- // 清理无用后缀,按逗号或分号切割
- const rawSegments = weekStr.replace(/周/g, '').split(/[,,;;]/);
-
- rawSegments.forEach(segment => {
- let isEven = segment.includes('双');
- let isOdd = segment.includes('单');
- let cleanSegment = segment.replace(/(单|双)/g, '').replace(/第/g, '').trim();
-
- if (cleanSegment.includes('-')) {
- const [start, end] = cleanSegment.split('-').map(Number);
- if (!isNaN(start) && !isNaN(end)) {
- for (let i = start; i <= end; i++) {
- if (isEven && i % 2 !== 0) continue;
- if (isOdd && i % 2 === 0) continue;
- weeks.add(i);
- }
- }
- } else {
- const num = Number(cleanSegment);
- if (!isNaN(num) && num > 0) {
- weeks.add(num);
- }
- }
- });
-
- return Array.from(weeks).sort((a, b) => a - b);
- }
- /**
- * 解析节次文本
- */
- function parseSectionText(sectionStr) {
- let startSection = 1;
- let endSection = 1;
- if (!sectionStr) return { startSection, endSection };
-
- const match = sectionStr.match(/(\d+)-(\d+)节?/);
- if (match) {
- startSection = parseInt(match[1]);
- endSection = parseInt(match[2]);
- } else {
- const singleMatch = sectionStr.match(/(\d+)节?/);
- if (singleMatch) {
- startSection = parseInt(singleMatch[1]);
- endSection = parseInt(singleMatch[1]);
- }
- }
- return { startSection, endSection };
- }
- /**
- * 从表格中动态解析时间段信息 (寻找 id="0_x" 的 th)
- */
- function parseTimeSlots() {
- let timeSlots = [];
- const timeThs = document.querySelectorAll('th[id^="0_"]');
-
- timeThs.forEach(th => {
- const idParts = th.id.split('_');
- const sectionNumber = parseInt(idParts[1]); // 获取节次序号
- const text = th.textContent || "";
-
- // 匹配格式如 "(08:00-08:45)"
- const timeMatch = text.match(/\((\d{2}:\d{2})-(\d{2}:\d{2})\)/);
- if (timeMatch && !isNaN(sectionNumber)) {
- timeSlots.push({
- number: sectionNumber,
- startTime: timeMatch[1],
- endTime: timeMatch[2]
- });
- }
- });
-
- return timeSlots.sort((a, b) => a.number - b.number);
- }
- /**
- * 核心解析:基于 HTML DOM 结构解析课程数据
- */
- async function fetchAndParseJwData() {
- try {
- AndroidBridge.showToast("正在解析网页课表...");
-
- let courses = [];
-
- // 获取所有带有有效 id (格式如 2_5) 且内部包含课程块的单元格
- const allTds = document.querySelectorAll('td[id*="_"]');
-
- allTds.forEach(td => {
- const idParts = td.id.split('_');
- if (idParts.length !== 2) return;
-
- // _前面从1到7代表星期
- const day = parseInt(idParts[0]);
- if (isNaN(day) || day < 1 || day > 7) return;
- // 找到单元格内所有的课程卡片
- const classDivs = td.querySelectorAll('.class_div');
-
- classDivs.forEach(div => {
- const pTags = div.querySelectorAll('p');
- if (pTags.length < 5) return; // 格式不健全的格子直接跳过
- const name = pTags[0].textContent.trim();
- const teacher = pTags[2].textContent.replace(/^[\s*]+|[\s*]+$/g, '').replace(/\*/g, ' ').replace(/\s+/g, ' ');
- const weekStr = pTags[3].textContent.trim();
- const sectionStr = pTags[4].textContent.trim();
- const position = pTags[5] ? pTags[5].textContent.trim() : "未知地点";
-
- // 解析周次与真实的开始/结束节次
- const weeks = parseWeekText(weekStr);
- const { startSection, endSection } = parseSectionText(sectionStr);
-
- if (name && weeks.length > 0) {
- courses.push({
- name: name,
- teacher: teacher,
- position: position,
- day: day,
- startSection: startSection,
- endSection: endSection,
- weeks: weeks
- });
- }
- });
- });
- // 动态提取时间段
- const timeSlots = parseTimeSlots();
- if (courses.length === 0) {
- throw new Error("未能在当前页面检测到有效的课表数据,请确认是否处于课表视图页面");
- }
- return { courses, timeSlots };
- } catch (e) {
- console.error("HTML解析失败详情:", e);
- AndroidBridge.showToast("同步失败: " + e.message);
- return null;
- }
- }
- /**
- * 辅助:保存数据到外部 APP
- */
- async function saveToApp(result) {
- const courseSuccess = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(result.courses));
- if (!courseSuccess) return false;
- if (result.timeSlots && result.timeSlots.length > 0) {
- await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(result.timeSlots));
- }
- return true;
- }
- /**
- * 流程控制流程
- */
- async function runImportFlow() {
- const alertResult = await window.AndroidBridgePromise.showAlert(
- "教务网页课表导入",
- "请确保您当前的网页已加载出课表视图后再开始导入",
- "开始同步"
- );
- if (!alertResult) return;
- const result = await fetchAndParseJwData();
- if (!result || result.courses.length === 0) return;
- if (await saveToApp(result)) {
- AndroidBridge.showToast(`成功从网页导入 ${result.courses.length} 个课程时段`);
- AndroidBridge.notifyTaskCompletion();
- }
- }
- // 启动导入流程
- runImportFlow();
|