Sfoglia il codice sorgente

Merge pull request #925 from ShiGuangSchedule/pending

星河欲转 1 giorno fa
parent
commit
f91b95a221

+ 5 - 0
index/root_index.yaml

@@ -1367,3 +1367,8 @@ schools:
     name: "武汉工程大学"
     initial: "W"
     resource_folder: "WIT"
+
+  - id: "GXSTNU"
+    name: "广西科技师范学院"
+    initial: "G"
+    resource_folder: "GXSTNU"

+ 9 - 0
resources/GXSTNU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/GXSTNU/adapters.yaml
+adapters:
+  - adapter_id: "GXSTNU_01"
+    adapter_name: "广西科技师范学院强智教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "gxstnu_01.js"
+    import_url: "https://jw.gxstnu.edu.cn/jsxsd/"
+    maintainer: "CikeSeven"
+    description: "广西科技师范学院强智教务(强智2020后版本)课表导入,登录教务系统后直接点击导入,可选择学期,支持获取开学日期与总周数"

+ 427 - 0
resources/GXSTNU/gxstnu_01.js

@@ -0,0 +1,427 @@
+// 广西科技师范学院(jw.gxstnu.edu.cn) 拾光课程表适配脚本
+// 强智2020后版本,结构与 HNU_01 相同:课表数据位于 class='qz-weeklyTable' 的表格中(表格无 id)
+//   - 学期列表 / 开学日期 / 总周数:教学周历 /jsxsd/jxzl/jxzl_query
+//   - 课程:学期理论课表 /jsxsd/xskb/xskb_list.do?viweType=0&xnxq01id=…
+//   - 作息:教务处《广西科技师范学院课表节次时间对应表》,硬编码
+// 出现问题请提交 issue / PR 反馈。
+
+// 整体包在 IIFE 里:部分客户端直接把脚本 eval 进页面全局作用域,
+// 同一页面再次执行导入时,顶层 const 会因重复声明而报错
+(function () {
+    const BASE_URL = "https://jw.gxstnu.edu.cn";
+    const URL_SEMESTER = `${BASE_URL}/jsxsd/jxzl/jxzl_query`; // 教学周历(学期列表 + 开学日期 + 最大周次)
+    const URL_COURSE = `${BASE_URL}/jsxsd/xskb/xskb_list.do`; // 学期理论课表
+
+    // 小节作息(教务处《广西科技师范学院课表节次时间对应表》)
+    const TIME_SLOTS = [
+        { "number": 1, "startTime": "08:20", "endTime": "09:00" },
+        { "number": 2, "startTime": "09:05", "endTime": "09:45" },
+        { "number": 3, "startTime": "09:55", "endTime": "10:35" },
+        { "number": 4, "startTime": "10:45", "endTime": "11:25" },
+        { "number": 5, "startTime": "11:30", "endTime": "12:10" },
+        { "number": 6, "startTime": "14:30", "endTime": "15:10" },
+        { "number": 7, "startTime": "15:15", "endTime": "15:55" },
+        { "number": 8, "startTime": "16:10", "endTime": "16:50" },
+        { "number": 9, "startTime": "16:55", "endTime": "17:35" },
+        { "number": 10, "startTime": "19:30", "endTime": "20:10" },
+        { "number": 11, "startTime": "20:15", "endTime": "20:55" },
+        { "number": 12, "startTime": "21:00", "endTime": "21:40" }
+    ];
+
+    const WEEKDAY = { "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "日": 7, "天": 7 };
+
+    // 教师字段带职称(如「张三讲师(高校)」「李四(校企)工程师」),只保留姓名;长的放前面
+    const TEACHER_TITLES = [
+        "助理研究员", "高级工程师", "高级实验师", "助理实验师", "副研究员",
+        "研究员", "实验师", "工程师", "副教授", "教授", "讲师", "助教", "其他", "其它"
+    ];
+
+    // 工具函数
+
+    function text(node) {
+        return node ? (node.textContent || "").replace(/\s+/g, " ").trim() : "";
+    }
+
+    function parseHtml(html) {
+        return new DOMParser().parseFromString(html, "text/html");
+    }
+
+    function pad2(n) {
+        return String(n).padStart(2, "0");
+    }
+
+    // "1-8,10-13" "11" "1-15(单)" "2-16双" → 周次数组
+    function parseWeeks(weekStr) {
+        const weeks = new Set();
+        String(weekStr || "").replace(/\s+/g, "").split(/[,,、]/).forEach(seg => {
+            const m = seg.match(/(\d+)(?:-(\d+))?/);
+            if (!m) return;
+            const a = Number(m[1]);
+            const b = m[2] ? Number(m[2]) : a;
+            const parity = /单/.test(seg) ? 1 : (/双/.test(seg) ? 2 : 0);
+            for (let w = Math.min(a, b); w <= Math.max(a, b); w++) {
+                if (w <= 0) continue;
+                if (parity === 1 && w % 2 === 0) continue;
+                if (parity === 2 && w % 2 === 1) continue;
+                weeks.add(w);
+            }
+        });
+        return [...weeks].sort((a, b) => a - b);
+    }
+
+    // 「时间:1-8,10-13周[1-3节]」→ [{ weeks, startSection, endSection }](兼容一条里多段)
+    function parseTimeText(timeStr) {
+        const result = [];
+        const re = /([^\[\]]*?)\[([^\]]*)\]/g;
+        let m;
+        while ((m = re.exec(timeStr)) !== null) {
+            const weeks = parseWeeks(m[1]);
+            const nums = m[2].split(/[^\d]+/).map(Number).filter(n => n > 0);
+            if (weeks.length && nums.length) {
+                result.push({ weeks: weeks, startSection: Math.min(...nums), endSection: Math.max(...nums) });
+            }
+        }
+        return result;
+    }
+
+    function cleanTeacher(raw) {
+        const names = String(raw || "").split(/[,,、]+/).map(part => {
+            const original = part.trim();
+            let name = original.replace(/[((][^()()]*[))]/g, "").trim();
+            for (const title of TEACHER_TITLES) {
+                if (name.endsWith(title) && name.length - title.length >= 2) {
+                    name = name.slice(0, -title.length).trim();
+                    break;
+                }
+            }
+            return name || original;
+        }).filter(Boolean);
+        return [...new Set(names)].join(",");
+    }
+
+    // "科教楼(西3-201-计算机基础实验室(12))" → "科教楼西3-201-计算机基础实验室(12)"(与教务首页写法一致)
+    function cleanPosition(raw) {
+        const str = String(raw || "").trim();
+        const m = str.match(/^([^()]*)\((.*)\)$/);
+        if (!m) return str;
+        const building = m[1].trim();
+        const room = m[2].trim();
+        if (!room) return building;
+        if (!building || room.startsWith(building)) return room;
+        return building + room;
+    }
+
+    /**
+     * 节次与周次合并去重函数(wiki「课程合并与去重函数」参考实现)
+     * 教务会把一门课拆成「1-10周[1-3节]」「11周[1-2节]」「11周[3节]」「12-16周[1-3节]」,合并后为「1-16周 1-3节」
+     */
+    function mergeAndDistinctCourses(courses) {
+        if (!Array.isArray(courses) || courses.length <= 1) return courses;
+
+        const grouped = {};
+        courses.forEach(c => {
+            const name = c.name || '';
+            const teacher = c.teacher || '';
+            const position = c.position || '';
+            const day = c.day || 0;
+            const key = `${day}|${name}|${teacher}|${position}`;
+            if (!grouped[key]) {
+                grouped[key] = { name, teacher, position, day, weeksMap: {} };
+            }
+            const weeksArr = Array.isArray(c.weeks) ? [...new Set(c.weeks)] : [];
+            weeksArr.forEach(w => {
+                if (!grouped[key].weeksMap[w]) grouped[key].weeksMap[w] = [];
+                grouped[key].weeksMap[w].push({ start: c.startSection, end: c.endSection });
+            });
+        });
+
+        const finalResult = [];
+        Object.values(grouped).forEach(group => {
+            const sortedWeeks = Object.keys(group.weeksMap).map(Number).sort((a, b) => a - b);
+            const processedWeeksMap = {};
+            sortedWeeks.forEach(w => {
+                const parts = group.weeksMap[w];
+                parts.sort((a, b) => a.start - b.start || a.end - b.end);
+                const mergedParts = [];
+                let cur = { ...parts[0] };
+                for (let i = 1; i < parts.length; i++) {
+                    if (cur.end + 1 >= parts[i].start) {
+                        cur.end = Math.max(cur.end, parts[i].end);
+                    } else {
+                        mergedParts.push(cur);
+                        cur = { ...parts[i] };
+                    }
+                }
+                mergedParts.push(cur);
+                processedWeeksMap[w] = mergedParts;
+            });
+
+            const sectionsMap = {};
+            sortedWeeks.forEach(w => {
+                const parts = processedWeeksMap[w];
+                const sig = parts.map(p => `${p.start}-${p.end}`).join('|');
+                if (!sectionsMap[sig]) sectionsMap[sig] = { sections: parts, weeks: [] };
+                sectionsMap[sig].weeks.push(w);
+            });
+
+            Object.values(sectionsMap).forEach(secGroup => {
+                secGroup.sections.forEach(sec => {
+                    finalResult.push({
+                        name: group.name,
+                        teacher: group.teacher,
+                        position: group.position,
+                        day: group.day,
+                        startSection: sec.start,
+                        endSection: sec.end,
+                        weeks: [...secGroup.weeks]
+                    });
+                });
+            });
+        });
+
+        finalResult.sort((a, b) => {
+            return (a.day || 0) - (b.day || 0) ||
+                (a.startSection || 0) - (b.startSection || 0) ||
+                a.name.localeCompare(b.name) ||
+                a.weeks[0] - b.weeks[0];
+        });
+        return finalResult;
+    }
+
+    // 核心解析逻辑
+
+    // 按 rowspan / colspan 把表格摊平,返回每个单元格的真实列号
+    function layoutRows(rows) {
+        const occupied = [];
+        const cells = [];
+        rows.forEach((row, r) => {
+            occupied[r] = occupied[r] || [];
+            let col = 0;
+            Array.from(row.cells).forEach(cell => {
+                while (occupied[r][col]) col++;
+                const rowSpan = Math.max(1, parseInt(cell.getAttribute("rowspan") || "1", 10) || 1);
+                const colSpan = Math.max(1, parseInt(cell.getAttribute("colspan") || "1", 10) || 1);
+                for (let dr = 0; dr < rowSpan; dr++) {
+                    occupied[r + dr] = occupied[r + dr] || [];
+                    for (let dc = 0; dc < colSpan; dc++) occupied[r + dr][col + dc] = true;
+                }
+                cells.push({ cell: cell, row: r, col: col });
+                col += colSpan;
+            });
+        });
+        return cells;
+    }
+
+    // 表头「星期一」…「星期日」→ { 列号: 星期 }
+    function parseHeaderDays(table) {
+        const dayByCol = {};
+        const headRow = table.tHead && table.tHead.rows[0];
+        if (!headRow) return dayByCol;
+        layoutRows([headRow]).forEach(item => {
+            const m = text(item.cell).replace(/\s/g, "").match(/(?:星期|周)([一二三四五六日天])/);
+            if (m) dayByCol[item.col] = WEEKDAY[m[1]];
+        });
+        return dayByCol;
+    }
+
+    // 读取一条课程:悬浮提示里的课程名与「老师:… / 时间:… / 地点:…」
+    function readCourseItem(tip) {
+        const info = {};
+        tip.querySelectorAll(".qz-tooltipContent-detailitem").forEach(item => {
+            const m = text(item).match(/^(老师|时间|地点)\s*[::]\s*(.*)$/);
+            if (m) info[m[1]] = m[2].trim();
+        });
+        return {
+            name: text(tip.querySelector(".qz-tooltipContent-title")),
+            teacher: cleanTeacher(info["老师"]),
+            position: cleanPosition(info["地点"]),
+            time: info["时间"] || ""
+        };
+    }
+
+    function parseTimetableToModel(doc) {
+        const table = doc.querySelector("table.qz-weeklyTable");
+        if (!table) return [];
+
+        const dayByCol = parseHeaderDays(table);
+        const bodyRows = Array.from(table.tBodies).flatMap(tbody => Array.from(tbody.rows));
+        const results = [];
+
+        layoutRows(bodyRows).forEach(item => {
+            if (item.cell.classList.contains("qz-weeklyTable-label")) return;
+            const day = dayByCol[item.col];
+            if (!day) return;
+
+            item.cell.querySelectorAll(".qz-tooltip li.qz-toolitiplists").forEach(tip => {
+                const course = readCourseItem(tip);
+                if (!course.name) return;
+                parseTimeText(course.time).forEach(seg => {
+                    results.push({
+                        name: course.name,
+                        teacher: course.teacher,
+                        position: course.position,
+                        day: day,
+                        startSection: seg.startSection,
+                        endSection: seg.endSection,
+                        weeks: seg.weeks
+                    });
+                });
+            });
+        });
+
+        return mergeAndDistinctCourses(results);
+    }
+
+    // 网络请求封装
+
+    async function requestText(url) {
+        const response = await fetch(url, { method: "GET", credentials: "include" });
+        if (!response.ok) throw new Error(`请求失败(HTTP ${response.status})`);
+        return await response.text();
+    }
+
+    // 教学周历的学期下拉框是无 method 的表单,按 GET 提交
+    async function requestSemesterPage(semesterId) {
+        const query = semesterId ? `?xnxq01id=${encodeURIComponent(semesterId)}` : "";
+        return parseHtml(await requestText(`${URL_SEMESTER}${query}`));
+    }
+
+    async function requestCoursePage(semesterId) {
+        const params = new URLSearchParams({ viweType: "0", xnxq01id: semesterId });
+        return parseHtml(await requestText(`${URL_COURSE}?${params.toString()}`));
+    }
+
+    // 业务逻辑
+
+    /**
+     * 获取学期列表,以 selected 为原点上下各取 3 个
+     * @returns {{list: Array<{value:string,label:string}>, defaultIndex:number}}
+     */
+    function parseSemesterList(doc) {
+        const select = doc.getElementById("xnxq01id");
+        if (!select) return { list: [], defaultIndex: -1 };
+
+        const allOptions = Array.from(select.querySelectorAll("option"))
+            .map(opt => ({
+                value: (opt.getAttribute("value") || "").trim(),
+                label: opt.textContent.trim(),
+                selected: opt.hasAttribute("selected")
+            }))
+            .filter(opt => opt.value);
+        if (allOptions.length === 0) return { list: [], defaultIndex: -1 };
+
+        let selectedIndex = allOptions.findIndex(o => o.selected);
+        if (selectedIndex === -1) selectedIndex = 0;
+        const start = Math.max(0, selectedIndex - 3);
+        const end = Math.min(allOptions.length, selectedIndex + 4);
+        return { list: allOptions.slice(start, end), defaultIndex: selectedIndex - start };
+    }
+
+    /**
+     * 从教学周历解析开学日期 + 最大周次 + 每周第一天
+     * 该校周历只有周末格子带月份(工作日为「07」,周末为「09月12日」),
+     * 因此取第1周行内第一个「MM月DD日」,按列倒推到该行第一天;
+     * 周历第一列有的学期是星期一,有的是星期日(如 2024-2025-2),每周第一天以第一列为准
+     */
+    function parseSemesterInfo(doc, semesterId) {
+        const table = doc.getElementById("dataTable");
+        if (!table) return { startDate: null, totalWeeks: null, firstDayOfWeek: null };
+
+        const dayByCol = parseHeaderDays(table);
+        const firstCol = Math.min(...Object.keys(dayByCol).map(Number));
+        const rows = Array.from(table.tBodies).flatMap(tbody => Array.from(tbody.rows));
+        const layout = layoutRows(rows);
+
+        let totalWeeks = null;
+        let week1Row = -1;
+        layout.forEach(item => {
+            if (!item.cell.classList.contains("qz-weeklyTable-label")) return;
+            const m = text(item.cell).match(/^第(\d+)周$/);
+            if (!m) return;
+            const n = Number(m[1]);
+            if (totalWeeks === null || n > totalWeeks) totalWeeks = n;
+            if (n === 1) week1Row = item.row;
+        });
+
+        // 学期 ID 形如「2024-2025-1」:第1学期在第一个年份开学,第2学期在第二个年份开学
+        const [firstYear, secondYear, term] = semesterId.split("-").map(Number);
+        const year = term === 1 ? firstYear : secondYear;
+
+        let startDate = null;
+        for (const item of layout) {
+            if (item.row !== week1Row || !dayByCol[item.col]) continue;
+            const m = text(item.cell).match(/(\d{1,2})月(\d{1,2})日/);
+            if (!m) continue;
+            const date = new Date(Date.UTC(year, Number(m[1]) - 1, Number(m[2])));
+            date.setUTCDate(date.getUTCDate() - (item.col - firstCol));
+            startDate = `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())}`;
+            break;
+        }
+
+        return { startDate, totalWeeks, firstDayOfWeek: dayByCol[firstCol] || null };
+    }
+
+    async function fetchCourses(semesterId) {
+        return parseTimetableToModel(await requestCoursePage(semesterId));
+    }
+
+    // 配置保存
+
+    async function saveAppConfig(semesterStartDate, totalWeeks, firstDayOfWeek) {
+        const config = {
+            "semesterTotalWeeks": totalWeeks || 20,
+            "firstDayOfWeek": firstDayOfWeek || 1
+        };
+        if (semesterStartDate) config.semesterStartDate = semesterStartDate;
+        return await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
+    }
+
+    async function saveAppTimeSlots() {
+        return await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(TIME_SLOTS));
+    }
+
+    // 流程编排
+
+    async function runImportFlow() {
+        try {
+            const confirmed = await window.shiguangBridgePromise.showAlert(
+                "提示", "请确保已成功登录教务系统。是否开始导入?", "开始"
+            );
+            if (!confirmed) return;
+
+            window.shiguangBridge.showToast("正在获取学期列表...");
+            const { list: semesters, defaultIndex } = parseSemesterList(await requestSemesterPage());
+            if (semesters.length === 0) {
+                window.shiguangBridge.showToast("未获取到学期列表,请检查登录状态。");
+                return;
+            }
+
+            const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
+                "选择学期", JSON.stringify(semesters.map(s => s.label)), defaultIndex
+            );
+            if (selectedIndex === null || selectedIndex === undefined || selectedIndex < 0) return;
+            const semesterId = semesters[selectedIndex].value;
+
+            window.shiguangBridge.showToast("正在获取学期信息...");
+            const { startDate, totalWeeks, firstDayOfWeek } = parseSemesterInfo(await requestSemesterPage(semesterId), semesterId);
+
+            window.shiguangBridge.showToast("正在请求课程数据...");
+            const finalCourses = await fetchCourses(semesterId);
+            if (finalCourses.length === 0) {
+                window.shiguangBridge.showToast("未发现课程,请检查学期选择或登录状态。");
+                return;
+            }
+
+            await saveAppConfig(startDate, totalWeeks, firstDayOfWeek);
+            await saveAppTimeSlots();
+            await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(finalCourses));
+
+            window.shiguangBridge.showToast(`成功导入 ${finalCourses.length} 条课程安排`);
+            window.shiguangBridge.notifyTaskCompletion();
+        } catch (error) {
+            window.shiguangBridge.showToast("异常: " + (error && error.message ? error.message : error));
+        }
+    }
+
+    runImportFlow();
+})();

+ 59 - 75
resources/zhengfang_jiaowu/zhengfang_01.js

@@ -6,20 +6,19 @@
 function parserTbale() {
     const regexName = /[●★○]/g;
     const courseInfoList = [];
-    const $ = window.jQuery; 
-    if (!$) return courseInfoList;
-
-    $('#kbgrid_table_0 td').each((i, td) => {
-        if ($(td).hasClass('td_wrap') && $(td).text().trim() !== '') {
-            const day = parseInt($(td).attr('id').split('-')[0]); 
-            
-            $(td).find('.timetable_con.text-left').each((i, course) => {
-                const name = $(course).find('.title font').text().replace(regexName, '').trim();
-                
-                const infoStr = $(course).find('p').eq(0).find('font').eq(1).text().trim(); 
-                
-                const position = $(course).find('p').eq(1).find('font').text().trim();
-                const teacher = $(course).find('p').eq(2).find('font').text().trim();
+
+    document.querySelectorAll('#kbgrid_table_0 td').forEach((td) => {
+        if (td.classList.contains('td_wrap') && td.textContent.trim() !== '') {
+            const day = parseInt(td.getAttribute('id').split('-')[0]);
+
+            td.querySelectorAll('.timetable_con.text-left').forEach((course) => {
+                const name = [...course.querySelectorAll('.title font')].map(e => e.textContent).join('').replace(regexName, '').trim();
+
+                const ps = course.querySelectorAll('p');
+                const infoStr = ps[0]?.querySelectorAll('font[color]')[1]?.textContent.trim() || '';
+                const position = ps[1] ? [...ps[1].querySelectorAll('font[color]')].map(e => e.textContent).join('').trim() : '';
+                const teacherRaw = ps[2] ? [...ps[2].querySelectorAll('font[color]')].map(e => e.textContent).join('').trim() : '';
+                const teacher = teacherRaw.replace(/[\((][^\))]*[\))]/g, '').trim();
 
                 if (infoStr && infoStr.match(/\((\d+-\d+节)\)/) && infoStr.split('节)')[1]) {
                     const [sections, weeks] = parserInfo(infoStr);
@@ -27,9 +26,9 @@ function parserTbale() {
                     if (name && position && teacher && sections.length && weeks.length) {
                         const startSection = sections[0];
                         const endSection = sections[sections.length - 1];
-                        
+
                         const finalPosition = position.split(/\s+/).pop();
-                        
+
                         const data = { name, day, weeks, teacher, position: finalPosition, startSection, endSection };
                         courseInfoList.push(data);
                     }
@@ -49,45 +48,46 @@ function parserList() {
     const regexPosition = /上课地点:/g;
     const regexTeacher = /教师 :/g;
 
-    const $ = window.jQuery; 
-    if (!$) return [];
-    
     let courseInfoList = [];
-    $('#kblist_table tbody').each((day, tbody) => {
-        if (day > 0 && day < 8) { 
+    document.querySelectorAll('#kblist_table tbody').forEach((tbody, day) => {
+        if (day > 0 && day < 8) {
             let sections;
-            $(tbody).find('tr:not(:first-child)').each((trIndex, tr) => {
+            tbody.querySelectorAll('tr:not(:first-child)').forEach((tr) => {
                 let name, font;
-                
-                if ($(tr).find('td').length > 1) { 
-                    sections = parserSections($(tr).find('td:first-child').text());
-                    name = $(tr).find('td:nth-child(2)').find('.title').text().replace(regexName, '').trim();
-                    font = $(tr).find('td:nth-child(2)').find('p font');
-                } else { 
-                    name = $(tr).find('td').find('.title').text().replace(regexName, '').trim();
-                    font = $(tr).find('td').find('p font');
+
+                if (tr.querySelectorAll('td').length > 1) {
+                    const firstTd = tr.querySelector('td:first-child');
+                    sections = parserSections(firstTd.textContent);
+                    const secondTd = tr.querySelector('td:nth-child(2)');
+                    name = [...secondTd.querySelectorAll('.title')].map(e => e.textContent).join('').replace(regexName, '').trim();
+                    font = secondTd.querySelectorAll('p font');
+                } else {
+                    const td = tr.querySelector('td');
+                    name = [...td.querySelectorAll('.title')].map(e => e.textContent).join('').replace(regexName, '').trim();
+                    font = td.querySelectorAll('p font');
                 }
-                
-                const weekStr = $(font[0]).text().replace(regexWeekNum, '').trim();
+
+                const weekStr = font[0] ? font[0].textContent.replace(regexWeekNum, '').trim() : '';
                 const weeks = parserWeeks(weekStr);
 
-                const positionRaw = $(font[1]).text().replace(regexPosition, '').trim(); 
+                const positionRaw = font[1] ? font[1].textContent.replace(regexPosition, '').trim() : '';
                 const finalPosition = positionRaw.split(/\s+/).pop();
-                
-                const teacher = $(font[2]).text().replace(regexTeacher, '').trim();
-                
+
+                const teacherRaw = font[2] ? font[2].textContent.replace(regexTeacher, '').trim() : '';
+                const teacher = teacherRaw.replace(/[\((][^\))]*[\))]/g, '').trim();
+
                 if (name && sections && weeks.length && teacher && finalPosition) {
                     const startSection = sections[0];
                     const endSection = sections[sections.length - 1];
-                    
-                    const data = { 
-                        name, 
-                        day, 
-                        weeks, 
-                        teacher, 
-                        position: finalPosition, 
-                        startSection, 
-                        endSection 
+
+                    const data = {
+                        name,
+                        day,
+                        weeks,
+                        teacher,
+                        position: finalPosition,
+                        startSection,
+                        endSection
                     };
                     courseInfoList.push(data);
                 }
@@ -125,19 +125,15 @@ function parserWeeks(str) {
     const segmentRegex = /(\d+)(?:-(\d+))?\s*(\([单双]\))?/g;
 
     for (const segment of segments) {
-        // 清理段落中的周字和多余空格
         const cleanSegment = segment.replace(/周/g, '').trim();
-        
-        // 重置正则的 lastIndex
         segmentRegex.lastIndex = 0;
 
         let match;
-        // 循环匹配一个段落内可能存在的所有周次定义(虽然通常只有一个)
         while ((match = segmentRegex.exec(cleanSegment)) !== null) {
             const start = parseInt(match[1]);
             const end = match[2] ? parseInt(match[2]) : start;
-            const flagStr = match[3] || ''; // (单) 或 (双) 或 ""
-            
+            const flagStr = match[3] || '';
+
             let flag = 0;
             if (flagStr.includes('单')) {
                 flag = 1;
@@ -146,11 +142,8 @@ function parserWeeks(str) {
             }
 
             for (let i = start; i <= end; i++) {
-                // 过滤单双周
-                if (flag === 1 && i % 2 !== 1) continue; // 仅保留单周
-                if (flag === 2 && i % 2 !== 0) continue; // 仅保留双周
-                
-                // 防止重复添加
+                if (flag === 1 && i % 2 !== 1) continue;
+                if (flag === 2 && i % 2 !== 0) continue;
                 if (!weeks.includes(i)) {
                     weeks.push(i);
                 }
@@ -158,7 +151,6 @@ function parserWeeks(str) {
         }
     }
 
-    // 排序并返回唯一的周次列表
     return weeks.sort((a, b) => a - b);
 }
 
@@ -170,27 +162,27 @@ async function scrapeAndParseCourses() {
         const text = await response.text();
         if (!text.includes("课表查询")) {
             console.log("页面内容检查失败!");
-            await window.shiguangBridgePromise.showAlert("导入失败", "当前页面似乎不是学生课表查询页面。请检查:\n" + ts, "确定"); 
+            await window.shiguangBridgePromise.showAlert("导入失败", "当前页面似乎不是学生课表查询页面。请检查:\n" + ts, "确定");
             return null;
         }
         const typeElement = document.querySelector('#shcPDF');
         if (!typeElement) {
-             console.log("未能找到视图类型元素 (#shcPDF)");
-             await window.shiguangBridgePromise.showAlert("导入失败", "未能识别课表视图类型,请确认您已点击查询且课表已加载完毕。", "确定");
-             return null;
+            console.log("未能找到视图类型元素 (#shcPDF)");
+            await window.shiguangBridgePromise.showAlert("导入失败", "未能识别课表视图类型,请确认您已点击查询且课表已加载完毕。", "确定");
+            return null;
         }
         const type = typeElement.dataset['type'];
         const tableElement = document.querySelector(type === 'list' ? '#kblist_table' : '#kbgrid_table_0');
         if (!tableElement) {
-             console.log("未能找到课表主体 HTML");
-             await window.shiguangBridgePromise.showAlert("导入失败", `未能找到课表主体 (${type} 视图),请确认您已点击查询且课表已加载完毕。`, "确定");
-             return null;
+            console.log("未能找到课表主体 HTML");
+            await window.shiguangBridgePromise.showAlert("导入失败", `未能找到课表主体 (${type} 视图),请确认您已点击查询且课表已加载完毕。`, "确定");
+            return null;
         }
         let result = [];
         if (type === 'list') {
-            result = parserList(); 
+            result = parserList();
         } else {
-            result = parserTbale(); 
+            result = parserTbale();
         }
         if (result.length === 0) {
             window.shiguangBridge.showToast("未找到任何课程数据,请检查所选学年学期是否正确或本学期无课。");
@@ -231,14 +223,6 @@ async function runImportFlow() {
         window.shiguangBridge.showToast("用户取消了导入。");
         return;
     }
-    
-    if (typeof window.jQuery === 'undefined' && typeof $ === 'undefined') {
-        const errorMsg = "当前教务系统页面似乎没有加载 jQuery 库。本脚本依赖 jQuery 进行 DOM 解析。";
-        window.shiguangBridge.showToast(errorMsg);
-        await window.shiguangBridgePromise.showAlert("导入失败", errorMsg + "\n请尝试刷新页面或使用其他导入方式。", "确定");
-        console.error("JS: 缺少 jQuery 依赖,流程终止。");
-        return;
-    }
 
     const result = await scrapeAndParseCourses();
     if (result === null) {
@@ -252,7 +236,7 @@ async function runImportFlow() {
         console.log("JS: 课程保存失败,流程终止。");
         return;
     }
-    
+
     window.shiguangBridge.showToast(`课程导入成功,共导入 ${courses.length} 门课程!`);
     console.log("JS: 整个导入流程执行完毕并成功。");
     window.shiguangBridge.notifyTaskCompletion();