Просмотр исходного кода

add: 成都信息工程大学(cuit) 教务适配 (#233)

* add: 成都信息工程大学(cuit) 教务适配

* fix: 对两个系统进行 API 支持

* style: update description
8070797671 2 недель назад
Родитель
Сommit
b13d6fd40f
4 измененных файлов с 595 добавлено и 11 удалено
  1. 16 11
      index/root_index.yaml
  2. 17 0
      resources/CUIT/adapters.yaml
  3. 245 0
      resources/CUIT/cuit_bk_new.js
  4. 317 0
      resources/CUIT/cuit_bk_old.js

+ 16 - 11
index/root_index.yaml

@@ -26,7 +26,7 @@ schools:
   - id: "urp_jiaowu"
   - id: "urp_jiaowu"
     name: "URP教务-通用教务"
     name: "URP教务-通用教务"
     initial: "U"
     initial: "U"
-    resource_folder: "urp_jiaowu"    
+    resource_folder: "urp_jiaowu"
 
 
   - id: "CQU"
   - id: "CQU"
     name: "重庆大学"
     name: "重庆大学"
@@ -43,6 +43,11 @@ schools:
     initial: "C"
     initial: "C"
     resource_folder: "CUST"
     resource_folder: "CUST"
 
 
+  - id: "CUIT"
+    name: "成都信息工程大学"
+    initial: "C"
+    resource_folder: "CUIT"
+
   - id: "SHZQ"
   - id: "SHZQ"
     name: "上海中侨职业技术大学"
     name: "上海中侨职业技术大学"
     initial: "S"
     initial: "S"
@@ -281,13 +286,13 @@ schools:
   - id: "HYNU"
   - id: "HYNU"
     name: "衡阳师范学院"
     name: "衡阳师范学院"
     initial: "H"
     initial: "H"
-    resource_folder: "HYNU"    
+    resource_folder: "HYNU"
+
+  - id: "JNU"
+    name: "暨南大学"
+    initial: "J"
+    resource_folder: "JNU"
 
 
-  - id: "JNU"    
-    name: "暨南大学" 
-    initial: "J"          
-    resource_folder: "JNU"   
-    
   - id: "HQU"
   - id: "HQU"
     name: "华侨大学"
     name: "华侨大学"
     initial: "H"
     initial: "H"
@@ -297,12 +302,12 @@ schools:
     name: "济南大学"
     name: "济南大学"
     initial: "J"
     initial: "J"
     resource_folder: "UJN"
     resource_folder: "UJN"
-    
+
   - id: "HBGUHX"
   - id: "HBGUHX"
     name: "河北地质大学华信学院"
     name: "河北地质大学华信学院"
     initial: "H"
     initial: "H"
     resource_folder: "HBGUHX"
     resource_folder: "HBGUHX"
-    
+
   - id: "YXHMC"
   - id: "YXHMC"
     name: "成都银杏酒店管理学院"
     name: "成都银杏酒店管理学院"
     initial: "C"
     initial: "C"
@@ -311,7 +316,7 @@ schools:
   - id: "HNIU"
   - id: "HNIU"
     name: "湖南信息职业技术学院"
     name: "湖南信息职业技术学院"
     initial: "H"
     initial: "H"
-    resource_folder: "HNIU"  
+    resource_folder: "HNIU"
 
 
   - id: "HUEL"
   - id: "HUEL"
     name: "河南财经政法大学"
     name: "河南财经政法大学"
@@ -342,7 +347,7 @@ schools:
     name: "河南信息科技学院"
     name: "河南信息科技学院"
     initial: "H"
     initial: "H"
     resource_folder: "HIIT"
     resource_folder: "HIIT"
-    
+
   - id: "NEUQ"
   - id: "NEUQ"
     name: "东北大学秦皇岛分校"
     name: "东北大学秦皇岛分校"
     initial: "D"
     initial: "D"

+ 17 - 0
resources/CUIT/adapters.yaml

@@ -0,0 +1,17 @@
+# resources/CUIT/adapters.yaml
+adapters:
+  - adapter_id: "CUIT_01"
+    adapter_name: "新版成都信息工程大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "cuit_bk_new.js"
+    import_url: "https://ywtb.cuit.edu.cn/"
+    maintainer: "csy214-beep(Pfolg)"
+    description: "【API】先进行登录,VPN:【一键连WebVPN】,导航【应用系统】->【本科实践教学平台】;导航到指定界面后,可以直接进行导入"
+
+  - adapter_id: "CUIT_02"
+    adapter_name: "旧版成都信息工程大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "cuit_bk_old.js"
+    import_url: "https://jwc.cuit.edu.cn/"
+    maintainer: "csy214-beep(Pfolg)"
+    description: "【API】登录后直接进行课表导入,界面不会完全加载成功"

+ 245 - 0
resources/CUIT/cuit_bk_new.js

@@ -0,0 +1,245 @@
+// == 成都信息工程大学(CUIT)课表适配脚本(Fetch API)==
+// 适用页面:https://sjjx.cuit.edu.cn:56443/labms/#/course/my
+// 适配实际 API 返回的扁平数组结构
+
+(async function () {
+  "use strict";
+
+  function showToast(msg) {
+    if (typeof AndroidBridge !== "undefined" && AndroidBridge.showToast) {
+      AndroidBridge.showToast(msg);
+    } else {
+      console.log("[Toast]", msg);
+    }
+  }
+
+  async function showAlert(title, content, confirmText = "确定") {
+    if (typeof window.AndroidBridgePromise !== "undefined") {
+      return await window.AndroidBridgePromise.showAlert(
+        title,
+        content,
+        confirmText,
+      );
+    } else {
+      alert(`${title}\n${content}`);
+      return true;
+    }
+  }
+
+  // ---------- 从页面提取用户信息 ----------
+  function getUserInfoFromPage() {
+    try {
+      const initialState = window.__INITIAL_STATE__ || window.g_initialState;
+      if (initialState?.info?.userCode) {
+        return {
+          status: 200,
+          data: {
+            userCode: initialState.info.userCode,
+            nickName: initialState.info.nickName || "",
+          },
+        };
+      }
+
+      const usernameSpan = document.querySelector(".username___LBEmQ");
+      if (usernameSpan) {
+        const text = usernameSpan.textContent.trim();
+        const idMatch = text.match(/^\d+/);
+        if (idMatch) {
+          return {
+            status: 200,
+            data: { userCode: idMatch[0], nickName: text },
+          };
+        }
+      }
+      return null;
+    } catch (e) {
+      console.warn("提取页面用户信息失败", e);
+      return null;
+    }
+  }
+
+  async function fetchUserInfo() {
+    const pageInfo = getUserInfoFromPage();
+    if (pageInfo) {
+      console.log("从页面全局变量获取用户信息成功");
+      return pageInfo;
+    }
+
+    const baseUrl = window.location.origin;
+    const url = `${baseUrl}/labms/user/info?sf_request_type=ajax`;
+    const resp = await fetch(url, {
+      method: "GET",
+      headers: { "X-Requested-With": "XMLHttpRequest" },
+      credentials: "include",
+    });
+    if (!resp.ok) throw new Error(`获取用户信息失败: ${resp.status}`);
+    const data = await resp.json();
+    if (data.status !== 200)
+      throw new Error(data.message || "获取用户信息失败");
+    return data;
+  }
+
+  // ---------- 获取当前学期 ----------
+  function getCurrentSemester() {
+    const selectItem = document.querySelector(
+      ".ant-select-selection-item[title]",
+    );
+    if (selectItem) {
+      const title = selectItem.getAttribute("title");
+      if (title?.includes("学年")) return title;
+    }
+    try {
+      const state = window.__INITIAL_STATE__ || window.g_initialState;
+      if (state?.semester?.current?.name) return state.semester.current.name;
+    } catch (e) {}
+    return "2025-2026学年第二学期";
+  }
+
+  // ---------- 请求课表数据 ----------
+  async function fetchCourseSchedule(studentId, semester) {
+    const baseUrl = window.location.origin;
+    const url = `${baseUrl}/labms/course/schedule/list/type?sf_request_type=ajax`;
+
+    const requestBody = {
+      studentIds: [studentId],
+      labIds: [],
+      classIds: [],
+      teacherIds: [studentId],
+      status: 2,
+      semester: semester,
+      week: null,
+      showMode: "table",
+      toBeDeleted: 0,
+    };
+
+    const resp = await fetch(url, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        "X-Requested-With": "XMLHttpRequest",
+      },
+      credentials: "include",
+      body: JSON.stringify(requestBody),
+    });
+
+    if (!resp.ok) throw new Error(`课表接口请求失败: ${resp.status}`);
+    const data = await resp.json();
+    if (data.status !== 200) throw new Error(data.message || "获取课表失败");
+    return data.data; // 直接返回数组
+  }
+
+  // ---------- 解析课表数据(扁平数组结构)----------
+  function convertApiDataToCourses(apiData) {
+    const courses = [];
+    if (!Array.isArray(apiData)) return courses;
+
+    for (const item of apiData) {
+      // 必须的字段校验
+      if (!item.courseName || !item.weeks || item.weeks.length === 0) continue;
+
+      const startSection =
+        item.sections && item.sections.length > 0 ? item.sections[0] : 1;
+      const endSection =
+        item.sections && item.sections.length > 0
+          ? item.sections[item.sections.length - 1]
+          : 1;
+
+      // 解析时间(去除秒)
+      let startTime = item.startTime ? item.startTime.substring(0, 5) : "";
+      let endTime = item.endTime ? item.endTime.substring(0, 5) : "";
+
+      courses.push({
+        name: item.courseName,
+        teacher: item.teacherName || "",
+        position: item.location || "",
+        day: item.weekDay, // 1=周一 ... 7=周日
+        startSection: startSection,
+        endSection: endSection,
+        weeks: item.weeks, // 数字数组,例如 [2,3,4,...]
+        isCustomTime: !!(startTime && endTime),
+        customStartTime: startTime,
+        customEndTime: endTime,
+      });
+    }
+
+    return courses;
+  }
+
+  // ---------- 导入预设时间段 ----------
+  async function importTimeSlots() {
+    const timeSlots = [
+      { number: 1, startTime: "08:20", endTime: "09:05" },
+      { number: 2, startTime: "09:15", endTime: "10:00" },
+      { number: 3, startTime: "10:20", endTime: "11:05" },
+      { number: 4, startTime: "11:15", endTime: "12:00" },
+      { number: 5, startTime: "14:00", endTime: "14:45" },
+      { number: 6, startTime: "14:55", endTime: "15:40" },
+      { number: 7, startTime: "15:50", endTime: "16:35" },
+      { number: 8, startTime: "16:45", endTime: "17:30" },
+      { number: 9, startTime: "17:40", endTime: "18:25" },
+      { number: 10, startTime: "19:30", endTime: "20:15" },
+      { number: 11, startTime: "20:25", endTime: "21:10" },
+      { number: 12, startTime: "21:20", endTime: "22:05" },
+    ];
+    await window.AndroidBridgePromise.savePresetTimeSlots(
+      JSON.stringify(timeSlots),
+    );
+  }
+
+  // ---------- 导入学期配置 ----------
+  async function importConfig(semester) {
+    let startDate = "2026-02-23";
+    if (semester.includes("2025-2026") && semester.includes("第一学期")) {
+      startDate = "2025-09-01";
+    }
+    const config = {
+      semesterStartDate: startDate,
+      semesterTotalWeeks: 20,
+      defaultClassDuration: 45,
+      defaultBreakDuration: 10,
+      firstDayOfWeek: 1,
+    };
+    await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
+  }
+
+  // ---------- 主流程 ----------
+  async function runImportFlow() {
+    try {
+      showToast("正在获取用户信息...");
+      const userInfo = await fetchUserInfo();
+      const studentId = userInfo.data.userCode;
+      const semester = getCurrentSemester();
+
+      if (!studentId) throw new Error("无法获取学号");
+
+      showToast(`正在获取 ${semester} 课表...`);
+      const apiData = await fetchCourseSchedule(studentId, semester);
+
+      showToast("正在解析课程数据...");
+      const courses = convertApiDataToCourses(apiData);
+      if (courses.length === 0) throw new Error("未解析到任何课程");
+
+      showToast(`解析到 ${courses.length} 门课程,正在保存...`);
+      await window.AndroidBridgePromise.saveImportedCourses(
+        JSON.stringify(courses),
+      );
+
+      await importTimeSlots();
+      await importConfig(semester);
+
+      showToast(`导入完成!共 ${courses.length} 门课程`);
+      if (
+        typeof AndroidBridge !== "undefined" &&
+        AndroidBridge.notifyTaskCompletion
+      ) {
+        AndroidBridge.notifyTaskCompletion();
+      }
+    } catch (error) {
+      console.error(error);
+      showToast(`导入失败: ${error.message}`);
+      await showAlert("导入失败", error.message);
+    }
+  }
+
+  setTimeout(runImportFlow, 800);
+})();

+ 317 - 0
resources/CUIT/cuit_bk_old.js

@@ -0,0 +1,317 @@
+/**
+ * 成都信息工程大学(树维教务)课表导入适配脚本 Fetch API
+ */
+(function() {
+    const BASE = "http://jwgl.cuit.edu.cn";
+
+    // ==================== 工具函数 ====================
+    function unquoteJsLiteral(token) {
+        const text = String(token || "").trim();
+        if (!text) return "";
+        if (text === "null" || text === "undefined") return "";
+        if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
+            return text.slice(1, -1);
+        }
+        return text;
+    }
+
+    function splitJsArgs(argsText) {
+        const args = [];
+        let curr = "";
+        let inQuote = "";
+        let escaped = false;
+        for (let i = 0; i < argsText.length; i++) {
+            const ch = argsText[i];
+            if (escaped) { curr += ch; escaped = false; continue; }
+            if (ch === "\\") { curr += ch; escaped = true; continue; }
+            if (inQuote) { curr += ch; if (ch === inQuote) inQuote = ""; continue; }
+            if (ch === '"' || ch === "'") { curr += ch; inQuote = ch; continue; }
+            if (ch === ",") { args.push(curr.trim()); curr = ""; continue; }
+            curr += ch;
+        }
+        if (curr.trim() || argsText.endsWith(",")) args.push(curr.trim());
+        return args;
+    }
+
+    function parseValidWeeksBitmap(bitmap) {
+        if (!bitmap || typeof bitmap !== "string") return [];
+        const weeks = [];
+        for (let i = 0; i < bitmap.length; i++) {
+            if (bitmap[i] === "1") weeks.push(i + 1);
+        }
+        return weeks;
+    }
+
+    function cleanCourseName(name) {
+        return String(name || "").replace(/\(\d{10}\.\d{2}\)\s*$/, "").trim();
+    }
+
+    // ==================== 核心解析 ====================
+    function parseCoursesFromHtml(htmlText) {
+        const text = String(htmlText || "");
+        if (!text) return [];
+
+        const unitCountMatch = text.match(/\bvar\s+unitCount\s*=\s*(\d+)\s*;/);
+        const unitCount = unitCountMatch ? parseInt(unitCountMatch[1], 10) : 12;
+
+        // 第一步:提取所有 actTeachers 定义块(用于解析教师姓名)
+        const teacherBlocks = [];
+        const teacherBlockRe = /var\s+teachers\s*=\s*\[(.*?)\];\s*var\s+actTeachers\s*=\s*\[(.*?)\];/gs;
+        let tbMatch;
+        while ((tbMatch = teacherBlockRe.exec(text)) !== null) {
+            const teachersArrStr = tbMatch[1];
+            const actTeachersArrStr = tbMatch[2];
+            const nameRe = /name\s*:\s*(?:"([^"]*)"|'([^']*)')/g;
+            const names = [];
+            let nm;
+            const searchStr = actTeachersArrStr || teachersArrStr;
+            while ((nm = nameRe.exec(searchStr)) !== null) {
+                const name = (nm[1] || nm[2] || "").trim();
+                if (name) names.push(name);
+            }
+            if (names.length > 0) {
+                teacherBlocks.push({
+                    startIndex: tbMatch.index,
+                    endIndex: tbMatch.index + tbMatch[0].length,
+                    teacherNames: names.join(',')
+                });
+            }
+        }
+
+        // 第二步:解析 TaskActivity 及 index 赋值,收集每门课的所有节次
+        // 使用 Map 键为 "name|teacher|position|day|weeks" 值,存储节次数组
+        const courseSectionsMap = new Map();
+        const blockRe = /activity\s*=\s*new\s+TaskActivity\(([^]*?)\)\s*;([\s\S]*?)(?=activity\s*=\s*new\s+TaskActivity|$)/g;
+        let match;
+
+        while ((match = blockRe.exec(text)) !== null) {
+            const argsText = match[1];
+            const afterBlock = match[2];
+            const blockStart = match.index;
+
+            const args = splitJsArgs(argsText);
+            if (args.length < 7) continue;
+
+            let teacherExpr = args[1];
+            const courseFull = unquoteJsLiteral(args[2]);
+            let courseNameRaw = unquoteJsLiteral(args[3]);
+            const classroom = unquoteJsLiteral(args[5]);
+            const weekBitmap = unquoteJsLiteral(args[6]);
+
+            let courseName = courseNameRaw || courseFull.replace(/\(.*\)/, "");
+            courseName = cleanCourseName(courseName);
+            if (!courseName) continue;
+
+            const weeks = parseValidWeeksBitmap(weekBitmap);
+            if (weeks.length === 0) continue;
+
+            // 解析教师姓名
+            let teacherNames = "";
+            const teacherExprStr = String(teacherExpr).trim();
+            if (teacherExprStr.includes('join') || teacherExprStr.includes('actTeacherName')) {
+                for (let i = teacherBlocks.length - 1; i >= 0; i--) {
+                    const tb = teacherBlocks[i];
+                    if (tb.startIndex < blockStart) {
+                        teacherNames = tb.teacherNames;
+                        break;
+                    }
+                }
+            } else {
+                teacherNames = unquoteJsLiteral(teacherExpr);
+            }
+
+            // 提取该 activity 被赋值的所有 index,得到 day 和 section
+            const indexRe = /index\s*=\s*(\d+)\s*\*\s*unitCount\s*\+\s*(\d+)\s*;/g;
+            let idxMatch;
+            while ((idxMatch = indexRe.exec(afterBlock)) !== null) {
+                const dayIdx = parseInt(idxMatch[1], 10);
+                const sectionIdx = parseInt(idxMatch[2], 10);
+                const day = dayIdx + 1;
+                const section = sectionIdx + 1;
+
+                // 唯一键(不含节次)
+                const baseKey = `${courseName}|${teacherNames}|${classroom}|${day}|${weeks.join(',')}`;
+
+                if (!courseSectionsMap.has(baseKey)) {
+                    courseSectionsMap.set(baseKey, {
+                        name: courseName,
+                        teacher: teacherNames,
+                        position: classroom,
+                        day: day,
+                        weeks: weeks,
+                        sections: new Set()
+                    });
+                }
+                courseSectionsMap.get(baseKey).sections.add(section);
+            }
+        }
+
+        // 第三步:将节次 Set 转换为连续区间,生成最终课程列表
+        const courses = [];
+        for (const [_, data] of courseSectionsMap) {
+            const sections = Array.from(data.sections).sort((a, b) => a - b);
+            if (sections.length === 0) continue;
+
+            // 分组连续节次
+            let start = sections[0];
+            let end = sections[0];
+            for (let i = 1; i < sections.length; i++) {
+                if (sections[i] === end + 1) {
+                    end = sections[i];
+                } else {
+                    courses.push({
+                        name: data.name,
+                        teacher: data.teacher,
+                        position: data.position,
+                        day: data.day,
+                        startSection: start,
+                        endSection: end,
+                        weeks: data.weeks,
+                        isCustomTime: false
+                    });
+                    start = sections[i];
+                    end = sections[i];
+                }
+            }
+            // 最后一组
+            courses.push({
+                name: data.name,
+                teacher: data.teacher,
+                position: data.position,
+                day: data.day,
+                startSection: start,
+                endSection: end,
+                weeks: data.weeks,
+                isCustomTime: false
+            });
+        }
+
+        return courses;
+    }
+
+    // ==================== 学期与入口参数解析(同前)====================
+    async function requestText(url, options) {
+        const res = await fetch(url, { credentials: "include", ...options });
+        if (!res.ok) throw new Error(`请求失败: ${res.status}`);
+        return await res.text();
+    }
+
+    function parseEntryParams(entryHtml) {
+        const idsMatch = entryHtml.match(/bg\.form\.addInput\(form,"ids","(\d+)"\)/);
+        const tagIdMatch = entryHtml.match(/id="(semesterBar\d+Semester)"/);
+        return {
+            studentId: idsMatch ? idsMatch[1] : "",
+            tagId: tagIdMatch ? tagIdMatch[1] : ""
+        };
+    }
+
+    function parseSemesterResponse(rawText) {
+        let data;
+        try {
+            data = Function(`return (${String(rawText).trim()});`)();
+        } catch {
+            throw new Error("学期数据解析失败");
+        }
+        const semesters = [];
+        if (!data || !data.semesters) return semesters;
+        Object.keys(data.semesters).forEach(k => {
+            const arr = data.semesters[k];
+            if (!Array.isArray(arr)) return;
+            arr.forEach(s => {
+                if (!s || !s.id) return;
+                semesters.push({
+                    id: String(s.id),
+                    name: `${s.schoolYear || ""} ${s.name || ""}学期`.trim()
+                });
+            });
+        });
+        return semesters;
+    }
+
+    function getPresetTimeSlots() {
+        return [
+            { number: 1, startTime: "08:20", endTime: "09:05" },
+            { number: 2, startTime: "09:15", endTime: "10:00" },
+            { number: 3, startTime: "10:20", endTime: "11:05" },
+            { number: 4, startTime: "11:15", endTime: "12:00" },
+            { number: 5, startTime: "14:00", endTime: "14:45" },
+            { number: 6, startTime: "14:55", endTime: "15:40" },
+            { number: 7, startTime: "15:50", endTime: "16:35" },
+            { number: 8, startTime: "16:45", endTime: "17:30" },
+            { number: 9, startTime: "17:40", endTime: "18:25" },
+            { number: 10, startTime: "19:30", endTime: "20:15" },
+            { number: 11, startTime: "20:25", endTime: "21:10" },
+            { number: 12, startTime: "21:20", endTime: "22:05" }
+        ];
+    }
+
+    // ==================== 主导入流程 ====================
+    async function runImportFlow() {
+        if (!window.AndroidBridgePromise) throw new Error("AndroidBridgePromise 不可用");
+        AndroidBridge.showToast("正在探测教务参数...");
+
+        const entryHtml = await requestText(`${BASE}/eams/courseTableForStd.action?&sf_request_type=ajax`, {
+            method: "GET",
+            headers: { "x-requested-with": "XMLHttpRequest" }
+        });
+        const params = parseEntryParams(entryHtml);
+        if (!params.studentId || !params.tagId) {
+            await window.AndroidBridgePromise.showAlert("参数探测失败", "未能识别学生ID或学期组件", "确定");
+            return;
+        }
+
+        const semesterRaw = await requestText(`${BASE}/eams/dataQuery.action?sf_request_type=ajax`, {
+            method: "POST",
+            headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
+            body: `tagId=${encodeURIComponent(params.tagId)}&dataType=semesterCalendar`
+        });
+        const allSemesters = parseSemesterResponse(semesterRaw);
+        if (allSemesters.length === 0) throw new Error("学期列表为空");
+        const recentSemesters = allSemesters.slice(-8);
+        const selectIndex = await window.AndroidBridgePromise.showSingleSelection(
+            "请选择导入学期",
+            JSON.stringify(recentSemesters.map(s => s.name || s.id)),
+            recentSemesters.length - 1
+        );
+        if (selectIndex === null) {
+            AndroidBridge.showToast("已取消导入");
+            return;
+        }
+        const selectedSemester = recentSemesters[selectIndex];
+        AndroidBridge.showToast("正在获取课表数据...");
+
+        const courseHtml = await requestText(`${BASE}/eams/courseTableForStd!courseTable.action?sf_request_type=ajax`, {
+            method: "POST",
+            headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
+            body: [
+                "ignoreHead=1",
+                "setting.kind=std",
+                "startWeek=",
+                `semester.id=${encodeURIComponent(selectedSemester.id)}`,
+                `ids=${encodeURIComponent(params.studentId)}`
+            ].join("&")
+        });
+
+        const courses = parseCoursesFromHtml(courseHtml);
+        if (courses.length === 0) {
+            await window.AndroidBridgePromise.showAlert("解析失败", "未提取到课程数据", "确定");
+            return;
+        }
+
+        await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        AndroidBridge.showToast(`成功导入 ${courses.length} 门课程`);
+
+        await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(getPresetTimeSlots()));
+
+        AndroidBridge.notifyTaskCompletion();
+    }
+
+    (async function bootstrap() {
+        try {
+            await runImportFlow();
+        } catch (error) {
+            console.error("导入流程失败:", error);
+            AndroidBridge.showToast("导入失败: " + error.message);
+        }
+    })();
+})();