|
|
@@ -0,0 +1,213 @@
|
|
|
+/**
|
|
|
+ * 基础工具函数:Base64 编码 (用于生成 URL 中的 params 参数)
|
|
|
+ */
|
|
|
+function encodeParams(xn, xq) {
|
|
|
+ const rawStr = `xn=${xn}&xq=${xq}`;
|
|
|
+ return btoa(rawStr);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 判断是否处于教务系统登录环境
|
|
|
+ */
|
|
|
+async function checkLoginEnvironment() {
|
|
|
+ const currentUrl = window.location.href;
|
|
|
+ const targetBase = "https://jws-443.webvpn.xmcu.edu.cn/xmcsjw/";
|
|
|
+
|
|
|
+ if (!currentUrl.startsWith(targetBase)) {
|
|
|
+ AndroidBridge.showToast("请先登录教务系统再进行导入");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 数据解析函数
|
|
|
+ */
|
|
|
+function parseAndMergeXmcuData(htmlText) {
|
|
|
+ const parser = new DOMParser();
|
|
|
+ const doc = parser.parseFromString(htmlText, 'text/html');
|
|
|
+ const rawItems = [];
|
|
|
+ const table = doc.getElementById('mytable');
|
|
|
+
|
|
|
+ if (!table) return [];
|
|
|
+
|
|
|
+ const rows = table.querySelectorAll('tr');
|
|
|
+ rows.forEach((row) => {
|
|
|
+ const cells = row.querySelectorAll('td.td');
|
|
|
+ cells.forEach((cell, dayIndex) => {
|
|
|
+ const day = dayIndex + 1;
|
|
|
+ const courseDivs = cell.querySelectorAll('div[style*="padding-bottom:5px"]');
|
|
|
+ courseDivs.forEach(div => {
|
|
|
+ const lines = Array.from(div.childNodes)
|
|
|
+ .map(n => n.textContent.trim())
|
|
|
+ .filter(t => t.length > 0);
|
|
|
+
|
|
|
+ if (lines.length >= 3) {
|
|
|
+ const match = lines[2].match(/(.*)\[(.*)\]/);
|
|
|
+ if (match) {
|
|
|
+ const weeks = [];
|
|
|
+ match[1].split(',').forEach(g => {
|
|
|
+ if (g.includes('-')) {
|
|
|
+ const [s, e] = g.split('-').map(Number);
|
|
|
+ for (let i = s; i <= e; i++) weeks.push(i);
|
|
|
+ } else { weeks.push(Number(g)); }
|
|
|
+ });
|
|
|
+ const sections = match[2].split('-').map(Number);
|
|
|
+ rawItems.push({
|
|
|
+ name: lines[0],
|
|
|
+ teacher: lines[1],
|
|
|
+ position: lines[3] || "未知地点",
|
|
|
+ day: day,
|
|
|
+ startSection: sections[0],
|
|
|
+ endSection: sections[sections.length - 1],
|
|
|
+ weeks: Array.from(new Set(weeks))
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ const groupMap = new Map();
|
|
|
+ rawItems.forEach(item => {
|
|
|
+ const key = `${item.name}|${item.teacher}|${item.position}|${item.day}`;
|
|
|
+ if (!groupMap.has(key)) groupMap.set(key, []);
|
|
|
+ groupMap.get(key).push(item);
|
|
|
+ });
|
|
|
+
|
|
|
+ const finalCourses = [];
|
|
|
+ groupMap.forEach((items, key) => {
|
|
|
+ const matrix = {};
|
|
|
+ items.forEach(item => {
|
|
|
+ item.weeks.forEach(w => {
|
|
|
+ if (!matrix[w]) matrix[w] = new Set();
|
|
|
+ for (let s = item.startSection; s <= item.endSection; s++) matrix[w].add(s);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ const patternMap = new Map();
|
|
|
+ Object.keys(matrix).forEach(w => {
|
|
|
+ const week = parseInt(w);
|
|
|
+ const sections = Array.from(matrix[week]).sort((a, b) => a - b);
|
|
|
+ let start = sections[0];
|
|
|
+ for (let i = 0; i < sections.length; i++) {
|
|
|
+ if (i === sections.length - 1 || sections[i+1] !== sections[i] + 1) {
|
|
|
+ const pKey = `${start}-${sections[i]}`;
|
|
|
+ if (!patternMap.has(pKey)) patternMap.set(pKey, []);
|
|
|
+ patternMap.get(pKey).push(week);
|
|
|
+ if (i < sections.length - 1) start = sections[i+1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const [name, teacher, position, day] = key.split('|');
|
|
|
+ patternMap.forEach((weeks, pKey) => {
|
|
|
+ const [sStart, sEnd] = pKey.split('-').map(Number);
|
|
|
+ finalCourses.push({
|
|
|
+ name, teacher, position,
|
|
|
+ day: parseInt(day),
|
|
|
+ startSection: sStart,
|
|
|
+ endSection: sEnd,
|
|
|
+ weeks: weeks.sort((a, b) => a - b)
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return finalCourses;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 学期获取函数
|
|
|
+ */
|
|
|
+async function getYearAndSemester() {
|
|
|
+ try {
|
|
|
+ AndroidBridge.showToast("正在获取学期列表...");
|
|
|
+ const response = await fetch("https://jws-443.webvpn.xmcu.edu.cn/xmcsjw/frame/droplist/getDropLists.action", {
|
|
|
+ method: "POST",
|
|
|
+ headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
|
|
|
+ body: "comboBoxName=StMsXnxqDxDesc¶mValue=&isYXB=0&isCDDW=0&isXQ=0&isDJKSLB=0&isZY=0",
|
|
|
+ credentials: "include"
|
|
|
+ });
|
|
|
+ const list = await response.json();
|
|
|
+ const names = list.map(item => item.name);
|
|
|
+ const selectedIndex = await window.AndroidBridgePromise.showSingleSelection("选择导入学期", JSON.stringify(names), 0);
|
|
|
+ if (selectedIndex === null) return null;
|
|
|
+ const [xn, xq] = list[selectedIndex].code.split('-');
|
|
|
+ return { xn, xq };
|
|
|
+ } catch (error) {
|
|
|
+ AndroidBridge.showToast("获取列表失败");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 课表抓取函数
|
|
|
+ */
|
|
|
+async function fetchCourses(xn, xq) {
|
|
|
+ try {
|
|
|
+ const paramsBase64 = encodeParams(xn, xq);
|
|
|
+ const url = `https://jws-443.webvpn.xmcu.edu.cn/xmcsjw/student/wsxk.xskcb10319.jsp?params=${paramsBase64}`;
|
|
|
+ AndroidBridge.showToast("正在提取数据...");
|
|
|
+ const response = await fetch(url, { method: "GET", credentials: "include" });
|
|
|
+ const arrayBuffer = await response.arrayBuffer();
|
|
|
+ const htmlText = new TextDecoder('gbk').decode(arrayBuffer);
|
|
|
+ return parseAndMergeXmcuData(htmlText);
|
|
|
+ } catch (error) {
|
|
|
+ AndroidBridge.showToast("抓取课表失败");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 时间段导入函数
|
|
|
+ */
|
|
|
+async function importPresetTimeSlots() {
|
|
|
+ const slots = [
|
|
|
+ { "number": 1, "startTime": "08:15", "endTime": "09:00" },
|
|
|
+ { "number": 2, "startTime": "09:05", "endTime": "09:50" },
|
|
|
+ { "number": 3, "startTime": "10:15", "endTime": "11:00" },
|
|
|
+ { "number": 4, "startTime": "11:05", "endTime": "11:50" },
|
|
|
+ { "number": 5, "startTime": "14:00", "endTime": "14:45" },
|
|
|
+ { "number": 6, "startTime": "14:50", "endTime": "15:35" },
|
|
|
+ { "number": 7, "startTime": "16:00", "endTime": "16:45" },
|
|
|
+ { "number": 8, "startTime": "16:50", "endTime": "17:35" },
|
|
|
+ { "number": 9, "startTime": "18:30", "endTime": "19:15" },
|
|
|
+ { "number": 10, "startTime": "19:15", "endTime": "20:00" },
|
|
|
+ { "number": 11, "startTime": "20:00", "endTime": "20:45" }
|
|
|
+ ];
|
|
|
+ await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(slots)).catch(() => {});
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 最终流程控制
|
|
|
+ */
|
|
|
+async function runImportFlow() {
|
|
|
+ // 环境检查 (非教务页面直接 Toast 退出)
|
|
|
+ const isReady = await checkLoginEnvironment();
|
|
|
+ if (!isReady) return;
|
|
|
+
|
|
|
+ // 弹窗确认
|
|
|
+ const confirmed = await window.AndroidBridgePromise.showAlert("教务导入", "建议在‘课表查询’页面进行导入以确保数据最全。", "确定");
|
|
|
+ if (!confirmed) return;
|
|
|
+
|
|
|
+ // 选择学期
|
|
|
+ const params = await getYearAndSemester();
|
|
|
+ if (!params) return;
|
|
|
+
|
|
|
+ // 获取并解析数据
|
|
|
+ const courses = await fetchCourses(params.xn, params.xq);
|
|
|
+ if (!courses || courses.length === 0) {
|
|
|
+ AndroidBridge.showToast("未找到有效课程");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 存储
|
|
|
+ await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
+ await importPresetTimeSlots();
|
|
|
+
|
|
|
+ // 完成
|
|
|
+ AndroidBridge.showToast(`导入成功:共 ${courses.length} 门课程`);
|
|
|
+ AndroidBridge.notifyTaskCompletion();
|
|
|
+}
|
|
|
+
|
|
|
+// 启动
|
|
|
+runImportFlow();
|