| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /**
- * 解析正方教务系统的周次字符串
- */
- function parseZhengFangWeeks(weekStr) {
- if (!weekStr) return [];
- let weeks = [];
- let isOdd = weekStr.includes('单');
- let isEven = weekStr.includes('双');
- let cleanStr = weekStr.replace(/周|\(单\)|\(双\)|单|双/g, '').replace(/\s+/g, '');
- let parts = cleanStr.split(',');
- for (let part of parts) {
- if (part.includes('-')) {
- let [start, end] = part.split('-');
- for (let i = parseInt(start); i <= parseInt(end); i++) {
- if (isOdd && i % 2 === 0) continue;
- if (isEven && i % 2 !== 0) continue;
- if (!weeks.includes(i)) weeks.push(i);
- }
- } else {
- let w = parseInt(part);
- if (!isNaN(w) && !weeks.includes(w)) {
- if (isOdd && w % 2 === 0) continue;
- if (isEven && w % 2 !== 0) continue;
- weeks.push(w);
- }
- }
- }
- return weeks.sort((a, b) => a - b);
- }
- /**
- * 封装兼容 WebVPN 的原生 AJAX 请求
- */
- function requestData(url, method = 'GET', data = null) {
- return new Promise((resolve, reject) => {
- let xhr = new XMLHttpRequest();
- xhr.open(method, url, true);
- if (method === 'POST') {
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- }
- xhr.onreadystatechange = function() {
- if (xhr.readyState === 4) {
- if (xhr.status >= 200 && xhr.status < 300) {
- resolve(xhr.responseText);
- } else {
- reject(new Error("网络请求失败,状态码:" + xhr.status));
- }
- }
- };
- xhr.onerror = function() {
- reject(new Error("网络请求发生错误,请检查网络连接。"));
- };
- xhr.send(data);
- });
- }
- /**
- * 用正则从 HTML 文本中硬核提取 select 选项
- */
- function extractOptionsByRegex(html, selectId) {
- let selectRegex = new RegExp(`<select[^>]*id="${selectId}"[^>]*>(.*?)<\/select>`, 'is');
- let selectMatch = html.match(selectRegex);
- if (!selectMatch) return [];
-
- let optionsHtml = selectMatch[1];
- let optionRegex = /<option\s+value="([^"]+)"([^>]*)>([^<]+)<\/option>/gi;
- let options = [];
- let match;
- while ((match = optionRegex.exec(optionsHtml)) !== null) {
- if (match[1].trim() !== "") {
- options.push({
- value: match[1].trim(),
- text: match[3].trim(),
- selected: match[2].includes('selected')
- });
- }
- }
- return options;
- }
- /**
- * 异步编排流程
- */
- async function runImportFlow() {
- try {
- if (typeof window.AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("正在通过 WebVPN 通道读取数据...");
- } else {
- console.log("【1/4】正在获取学期选项...");
- }
- let sysPath = typeof _path !== 'undefined' ? _path : '/jwglxt';
- let semesters = [];
- let semesterValues = [];
- let defaultIndex = 0;
- // 1. 尝试直接通过正则表达式,去后台请求并提取页面中的下拉框
- console.log("正在后台请求页面源码...");
- const indexHtml = await requestData(`${sysPath}/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default`, 'GET');
-
- let yearOpts = extractOptionsByRegex(indexHtml, 'xnm');
- let termOpts = extractOptionsByRegex(indexHtml, 'xqm');
- if (yearOpts.length === 0 || termOpts.length === 0) {
- throw new Error("源码提取学期信息失败,可能是 WebVPN 会话过期,请重新登录教务系统!");
- }
- let count = 0;
- yearOpts.forEach(y => {
- termOpts.forEach(t => {
- semesters.push(`${y.text} 第${t.text}学期`);
- semesterValues.push({ xnm: y.value, xqm: t.value });
- if (y.selected && t.selected) {
- defaultIndex = count;
- }
- count++;
- });
- });
- // 2. 弹出选择框
- let selectedIdx = defaultIndex;
- if (typeof window.AndroidBridgePromise !== 'undefined') {
- let userChoice = await window.AndroidBridgePromise.showSingleSelection(
- "请选择要导入的学期",
- JSON.stringify(semesters),
- defaultIndex
- );
- if (userChoice === null) {
- AndroidBridge.showToast("已取消导入");
- return;
- }
- selectedIdx = userChoice;
- } else {
- let msg = "【浏览器测试】请选择学期对应的序号:\n\n";
- semesters.forEach((s, idx) => {
- if (Math.abs(idx - defaultIndex) <= 4) msg += `[ ${idx} ] : ${s}\n`;
- });
- msg += "\n请输入方括号中的数字:";
- let userInput = prompt(msg, defaultIndex);
- if (userInput === null) return;
- selectedIdx = parseInt(userInput);
- if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= semesters.length) {
- alert("输入无效,使用默认学期!");
- selectedIdx = defaultIndex;
- }
- }
- const targetData = semesterValues[selectedIdx];
- if (typeof window.AndroidBridge !== 'undefined') {
- AndroidBridge.showToast(`正在获取 [${semesters[selectedIdx]}] 数据...`);
- } else {
- console.log(`【2/4】正在向服务器请求 [${semesters[selectedIdx]}] 的 JSON 数据...`);
- }
- // 3. 发送 Ajax 请求获取真实的课表和作息时间
- const postBody = `xnm=${targetData.xnm}&xqm=${targetData.xqm}&kzlx=ck&xsdm=&kclbdm=&kclxdm=`;
-
- const [kbResText, timeResText] = await Promise.all([
- requestData(`${sysPath}/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151`, 'POST', postBody),
- requestData(`${sysPath}/kbcx/xskbcx_cxRjc.html?gnmkdm=N2151`, 'POST', postBody)
- ]);
- const kbJson = JSON.parse(kbResText);
- const timeJson = JSON.parse(timeResText);
- // 4. 解析作息时间 (拦截 21:35 之后的时间)
- let timeSlots = [];
- if (Array.isArray(timeJson)) {
- timeJson.forEach(t => {
- if (t.jssj > "21:35") return;
- timeSlots.push({
- number: parseInt(t.jcmc, 10),
- startTime: t.qssj,
- endTime: t.jssj
- });
- });
- timeSlots.sort((a, b) => a.number - b.number);
- }
- // 5. 解析课程数据
- let parsedCourses = [];
- if (kbJson && kbJson.kbList) {
- kbJson.kbList.forEach(c => {
- let courseObj = {
- name: c.kcmc || "未知课程",
- teacher: c.xm || "未知",
- position: c.cdmc || "待定",
- day: parseInt(c.xqj),
- isCustomTime: false
- };
- courseObj.weeks = parseZhengFangWeeks(c.zcd);
- if (c.jcs) {
- let secParts = c.jcs.split('-');
- courseObj.startSection = parseInt(secParts[0]);
- courseObj.endSection = parseInt(secParts[secParts.length - 1] || secParts[0]);
- }
- if (courseObj.name && courseObj.weeks.length > 0 && courseObj.startSection) {
- parsedCourses.push(courseObj);
- }
- });
- }
- if (parsedCourses.length === 0) {
- const errMsg = "该学期暂无排课数据。";
- if (typeof window.AndroidBridgePromise !== 'undefined') {
- await window.AndroidBridgePromise.showAlert("提示", errMsg, "好的");
- } else alert(errMsg);
- return;
- }
- // 6. 去重
- let uniqueCourses = [];
- let courseSet = new Set();
- parsedCourses.forEach(course => {
- let uniqueKey = `${course.day}-${course.startSection}-${course.endSection}-${course.name}-${course.weeks.join(',')}`;
- if (!courseSet.has(uniqueKey)) {
- courseSet.add(uniqueKey);
- uniqueCourses.push(course);
- }
- });
- const config = {
- "defaultClassDuration": 45,
- "defaultBreakDuration": 5
- };
- // 7. 打印并保存
- if (typeof window.AndroidBridgePromise === 'undefined') {
- console.log("【测试成功】被 21:35 规则过滤后的作息时间表:\n", timeSlots);
- console.log(`【测试成功】共获取到 ${uniqueCourses.length} 门课程:\n`, JSON.stringify(uniqueCourses, null, 2));
- alert(`解析成功!获取到 ${uniqueCourses.length} 门课程及过滤后的作息时间。\n请打开F12控制台查看。`);
- return;
- }
- await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
- if (timeSlots.length > 0) {
- await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- }
-
- const saveResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(uniqueCourses));
- if (!saveResult) {
- AndroidBridge.showToast("保存失败,请重试!");
- return;
- }
- AndroidBridge.showToast(`成功导入 ${uniqueCourses.length} 节课程!`);
- AndroidBridge.notifyTaskCompletion();
- } catch (error) {
- if (typeof window.AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("导入异常: " + error.message);
- } else {
- console.error("【导入异常】", error);
- alert("导入异常: " + error.message);
- }
- }
- }
- runImportFlow();
|