| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- // 文件: capadap.js
- /**
- * 显示导入提示
- */
- async function promptUserToStart() {
- const confirmed = await window.AndroidBridgePromise.showAlert(
- "导入确认",
- "导入前请确保您已进入课表页面(运行->课表查询->我的课表)并等待页面加载完成",
- "开始导入"
- );
- if (!confirmed) {
- AndroidBridge.showToast("用户取消了导入");
- return false;
- }
- AndroidBridge.showToast("开始获取课表数据...");
- return true;
- }
- /**
- * 获取 iframe 内容
- */
- function getIframeDocument() {
- try {
- // 尝试多种选择器找到 iframe 以防修改
- const selectors = [
- '.iframe___1hsk7',
- '[class*="iframe"]',
- 'iframe'
- ];
-
- let iframe = null;
- for (const selector of selectors) {
- iframe = document.querySelector(selector);
- if (iframe) {
- console.log(`通过选择器 "${selector}" 找到 iframe`);
- break;
- }
- }
-
- if (!iframe) {
- console.error('未找到 iframe 元素');
- AndroidBridge.showToast("未找到课表框架,请确保在课表页面");
- return null;
- }
-
- // 获取 iframe 的 document
- const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
-
- if (!iframeDoc) {
- console.error('无法访问 iframe 内容');
- AndroidBridge.showToast("无法访问课表内容,可能页面未加载完成");
- return null;
- }
-
- // 检查是否包含课表元素
- const timetable = iframeDoc.querySelector('.kbappTimetableDayColumnRoot');
- if (!timetable) {
- console.warn('iframe 中未找到课表元素,可能不在课表页面');
- }
-
- return iframeDoc;
-
- } catch (error) {
- console.error('获取 iframe 内容时出错:', error);
- AndroidBridge.showToast(`获取课表失败: ${error.message}`);
- return null;
- }
- }
- /**
- * 计算每天课程节数
- **/
- function getSectionByPosition(element) {
- const dayColumn = element.closest('.kbappTimetableDayColumnRoot');
- const dayCols = Array.from(dayColumn.parentNode.children);
- const day = dayCols.indexOf(dayColumn) + 1;
- let slotBlock = element;
- while (slotBlock.parentElement && slotBlock.parentElement !== dayColumn) {
- slotBlock = slotBlock.parentElement;
- }
- const win = element.ownerDocument.defaultView || window;
- const getFlex = (el) => {
- const fg = win.getComputedStyle(el).flexGrow;
- return Math.round(parseFloat(fg || 0));
- };
- let previousFlexSum = 0;
- let curr = slotBlock.previousElementSibling;
- while (curr) {
- previousFlexSum += getFlex(curr);
- curr = curr.previousElementSibling;
- }
- const currentFlex = getFlex(slotBlock);
-
- // 换算
- let start = previousFlexSum + 1;
- let end = start + Math.max(1, currentFlex) - 1;
- // 这里的的start和end都加了午餐晚餐 午餐晚餐修正节数
- if (start >= 10) {
- start -= 2;
- end -= 2;
- } else if (start > 5) {
- start -= 1;
- end -= 1;
- }
- return { day, start, end };
- }
- /**
- * 解析时间段数据
- */
- function parseTimeSlots(iframeDoc) {
- const timeSlots = [];
-
- // 查找时间段列
- const timeColumn = iframeDoc.querySelector('.kbappTimetableJcColumn');
-
- const timeItems = timeColumn.querySelectorAll('.kbappTimetableJcItem');
-
- timeItems.forEach((item, index) => {
- const textElements = item.querySelectorAll('.kbappTimetableJcItemText');
- if (textElements.length >= 2) {
- const sectionName = textElements[0]?.textContent?.trim() || `第${index + 1}节`;
- const timeRange = textElements[1]?.textContent?.trim() || '';
-
- // 解析时间范围
- const timeMatch = timeRange.match(/(\d{2}:\d{2})[~-](\d{2}:\d{2})/);
- if (timeMatch) {
- const startTime = timeMatch[1];
- const endTime = timeMatch[2];
-
- // 提取节次数字
- const sectionMatch = sectionName.match(/第(\d+)节/);
- const sectionNumber = sectionMatch ? parseInt(sectionMatch[1]) : index + 1;
-
- timeSlots.push({
- number: sectionNumber,
- startTime: startTime,
- endTime: endTime
- });
-
- // console.log(`时间段 ${sectionNumber}: ${startTime} ~ ${endTime}`);
- }
- }
- });
-
- return timeSlots;
- }
- /**
- * 解析周次信息
- */
- function parseWeeks(text) {
- const weeks = [];
- // 匹配如 1-16, 1, 3, 5-7 等模式
- const patterns = text.match(/(\d+)-(\d+)周|(\d+)周/g);
- if (!patterns) return weeks;
- const isSingle = text.includes('(单)');
- const isDouble = text.includes('(双)');
- patterns.forEach(p => {
- const range = p.match(/(\d+)-(\d+)/);
- if (range) {
- const start = parseInt(range[1]);
- const end = parseInt(range[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 single = p.match(/(\d+)/);
- if (single) weeks.push(parseInt(single[1]));
- }
- });
- return weeks;
- }
- /**
- * 解析单个课程信息
- */
- // 这里源数据使用了el - popover 和el - popover__reference两种模式 一种是弹窗还要一种是课程块
- // 我这里解析就只用了第一种popover 因为显示的数据精简 直接可以使用
- function parseSingleCourse(courseElement, day, timeSlots) {
- try {
- const infoTexts = courseElement.querySelectorAll('.kbappTimetableCourseRenderCourseItemInfoText');
- if (infoTexts.length < 2) return null;
-
- // 课程名称
- let nameElement = courseElement.querySelector('.kbappTimetableCourseRenderCourseItemName');
- let rawName = nameElement ? nameElement.innerText.trim() : courseElement.innerText.split('\n')[0].trim();
- let name = rawName.replace(/\[.*?\]/g, "").replace(/\s+\d+$/, "").trim();
- if (name === "未知课程" || !name) return;
- // 获取持续时间
- const duration = parseInt(courseElement.getAttribute('data-scales-span') || '1');
-
- // 计算起始节次
- let startSection = 1;
- const parent = courseElement.closest('.kbappTimetableCourseRenderColumn');
- if (parent) {
- const containers = parent.querySelectorAll('.kbappTimetableCourseRenderCourseItemContainer');
- for (let i = 0; i < containers.length; i++) {
- const container = containers[i];
- const courseInContainer = container.querySelector('.kbappTimetableCourseRenderCourseItem');
- if (courseInContainer === courseElement) {
- const flexMatch = container.style.flex?.match(/(\d+)/);
- if (flexMatch) {
- let totalPrevSpan = 0;
- for (let j = 0; j < i; j++) {
- const prevFlex = containers[j].style.flex?.match(/(\d+)/);
- if (prevFlex) {
- totalPrevSpan += parseInt(prevFlex[1]);
- }
- }
- startSection = totalPrevSpan + 1;
- }
- break;
- }
- }
- }
-
- // 计算结束节次
- const endSection = startSection + duration - 1;
-
- // 验证范围
- const validStart = Math.max(1, Math.min(startSection, timeSlots?.length || 12));
- const validEnd = Math.max(validStart, Math.min(endSection, timeSlots?.length || 12));
-
- return {
- name: name,
- teacher: '未知教师', // 暂时用默认值
- position: '未知教室', // 暂时用默认值
- day: day,
- startSection: validStart,
- endSection: validEnd,
- weeks: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18], // 暂时用默认值
- isCustomTime: false
- };
-
- } catch (error) {
- // console.error('解析出错:', error);
- return null;
- }
- }
- /**
- * 解析课程数据
- */
- function parseCourses(iframeDoc, timeSlots) {
- const courses = [];
-
- // 获取所有星期列
- const dayColumns = iframeDoc.querySelectorAll('.kbappTimetableDayColumnRoot');
- // console.log('找到课表列数量:', dayColumns.length);
-
-
- // 遍历每一天的列
- for (let dayIndex = 0; dayIndex < dayColumns.length; dayIndex++) {
- const dayColumn = dayColumns[dayIndex];
-
- // 获取当天的所有课程
- const dayCourses = dayColumn.querySelectorAll('.kbappTimetableCourseRenderCourseItem');
-
- // console.log(`星期${dayIndex + 1} 课程数量:`, dayCourses.length);
-
- dayCourses.forEach(courseElement => {
- const courseInfo = parseSingleCourse(courseElement, dayIndex + 1, timeSlots);
- if (courseInfo) {
- courses.push(courseInfo);
- }
- });
- }
-
- return courses;
- }
- /**
- * 解析所有数据
- */
- function parseAllData(iframeDoc) {
- const timeSlots = parseTimeSlots(iframeDoc);
- const courses = [];
- const courseElements = iframeDoc.querySelectorAll('.kbappTimetableCourseRenderCourseItem');
- courseElements.forEach(element => {
- try {
- const popoverId = element.getAttribute('aria-describedby');
- const popover = iframeDoc.getElementById(popoverId);
- if (!popover) return;
- const nameElement = popover.querySelector('.kbappTimetableCourseRenderCourseItemInfoPopperInfo');
- const name = nameElement ? nameElement.textContent.trim().replace(/\[.*?\]/g, "") : "";
- if (!name) return;
- // 获取位置信息
- const sectionInfo = getSectionByPosition(element);
- // --- 关键修正:获取所有信息行 (处理单双周不同行的情况) ---
- const infoItems = Array.from(popover.querySelectorAll('.kbappTimetableCourseRenderCourseItemInfoPopperInfo')).slice(1);
-
- infoItems.forEach(item => {
- const detailStr = item.textContent.trim();
- if (!detailStr) return;
- const parts = detailStr.split(/\s+/).filter(p => p.length > 0);
- let teacher = "未知教师";
- let posParts = [];
- let currentWeeks = parseWeeks(detailStr);
- parts.forEach(p => {
- if (p.includes('周')) return;
- // 老师判定:2-4个字且不含地点特征词
- if (/^[\u4e00-\u9fa5]{2,4}$/.test(p) && !/(楼|校区|室|场|馆|中心)/.test(p)) {
- teacher = p;
- } else {
- posParts.push(p);
- }
- });
- // 地点去重:选最长的描述
- let position = posParts.sort((a, b) => b.length - a.length)[0] || "未知教室";
- courses.push({
- name: name,
- teacher: teacher,
- position: position,
- day: sectionInfo.day,
- startSection: sectionInfo.start,
- endSection: sectionInfo.end,
- weeks: currentWeeks
- });
- });
- } catch (e) { console.error("解析单条课程失败:", e); }
- });
- return { courses: removeDuplicates(courses), timeSlots };
- }
- /**
- * 课程去重 后期这里可能会出现问题
- */
- function removeDuplicates(courses) {
- const courseMap = new Map();
-
- courses.forEach(course => {
- // 生成唯一键(不包括周次)
- // 可以根据需要调整组合字段
- const key = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.position}`;
-
- if (courseMap.has(key)) {
- // 已存在:合并周次
- const existing = courseMap.get(key);
- // 合并并去重
- const combinedWeeks = [...existing.weeks, ...course.weeks];
- const uniqueWeeks = [...new Set(combinedWeeks)];
- // 排序
- existing.weeks = uniqueWeeks.sort((a, b) => a - b);
-
- // 如果需要,可以保留最早出现的教师(如果教师不同的话)
- // 但这里保持原有逻辑,不更新教师
- } else {
- // 不存在:添加新记录
- courseMap.set(key, {...course, weeks: [...course.weeks]});
- }
- });
-
- // 转换回数组
- return Array.from(courseMap.values());
- }
- /**
- * 保存课程数据
- */
- async function saveCourses(parsedData) {
- const { courses, timeSlots } = parsedData;
-
- try {
- AndroidBridge.showToast(`准备保存 ${courses.length} 门课程...`);
-
- // 保存课程数据
- const courseSaveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
- if (!courseSaveResult) {
- AndroidBridge.showToast("保存课程失败");
- return false;
- }
-
- AndroidBridge.showToast(`成功导入 ${courses.length} 门课程`);
-
- // 保存时间段数据
- if (timeSlots && timeSlots.length > 0) {
- const timeSlotSaveResult = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- if (timeSlotSaveResult) {
- AndroidBridge.showToast(`成功导入 ${timeSlots.length} 个时间段`);
- } else {
- AndroidBridge.showToast("时间段导入失败,课程仍可使用");
- }
- }
-
- return true;
- } catch (error) {
- console.error("保存课程数据时出错:", error);
- AndroidBridge.showToast(`保存失败: ${error.message}`);
- return false;
- }
- }
- /**
- * 运行主函数
- */
- async function runImportFlow() {
- try {
- AndroidBridge.showToast("课表导入工具启动...");
-
- // 1. 显示导入提示
- const shouldProceed = await promptUserToStart();
- if (!shouldProceed) return;
-
- // 2. 等待一下确保页面加载
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- // 3. 获取 iframe 内容
- const iframeDoc = getIframeDocument();
- if (!iframeDoc) return;
-
- // 4. 解析数据
- AndroidBridge.showToast("正在解析课表数据...");
- const parsedData = parseAllData(iframeDoc);
-
- if (parsedData.courses.length === 0) {
- await window.AndroidBridgePromise.showAlert(
- "解析失败",
- "未找到任何课程数据,请确认:\n1. 已在课表查询页面\n2. 课表已完全加载\n3. 当前学期有课程",
- "知道了"
- );
- return;
- }
-
- // 5. 显示预览
- const previewMsg = `找到 ${parsedData.courses.length} 门课程\n${parsedData.timeSlots.length} 个时间段\n\n是否继续导入?`;
- const confirmed = await window.AndroidBridgePromise.showAlert(
- "导入确认",
- previewMsg,
- "确认导入"
- );
-
- if (!confirmed) {
- AndroidBridge.showToast("已取消导入");
- return;
- }
-
- // 6. 保存数据
- const saveSuccess = await saveCourses(parsedData);
- if (!saveSuccess) return;
-
- // 7. 完成
- AndroidBridge.showToast("课表导入完成!");
- AndroidBridge.notifyTaskCompletion();
-
- } catch (error) {
- console.error("导入流程出错:", error);
- AndroidBridge.showToast(`导入失败: ${error.message}`);
- }
- }
- // 启动导入流程
- runImportFlow();
|