| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- // resources/CUP/cup_02.js
- // 中国石油大学(北京)拾光课程表适配脚本
- // https://gmis.cup.edu.cn/gmis/student/default/index
- // 教务平台:南京南软
- // 适配开发者:larryyan
- // ==========================================
- // 0. 全局配置与验证函数
- // ==========================================
- const PRESET_TIME_CONFIG = {
- campuses: {
- MainCampus: {
- startTimes: {
- morning: "08:00",
- afternoon: "13:30",
- evening: "18:30"
- },
- sectionCounts: {
- morning: 4,
- afternoon: 4,
- evening: 3
- },
- durations: {
- classMinutes: 45,
- shortBreakMinutes: 5,
- longBreakMinutes: 30
- }
- },
- Karamay: {
- startTimes: {
- morning: "09:30",
- afternoon: "16:00",
- evening: "20:30"
- },
- sectionCounts: {
- morning: 5,
- afternoon: 4,
- evening: 3
- },
- durations: {
- classMinutes: 45,
- shortBreakMinutes: 5,
- longBreakMinutes: 20
- },
- }
- },
- common: {
- longBreakAfter: {
- morning: 2,
- afternoon: 2,
- evening: 0 // 晚间课程无大课间
- }
- }
- };
- const CAMPUS_OPTIONS = [
- { id: "MainCampus", label: "主校区" },
- { id: "Karamay", label: "克拉玛依校区" }
- ];
- /**
- * 验证开学日期的输入格式
- */
- function validateDateInput(input) {
- if (/^\d{4}[-\/\.]\d{2}[-\/\.]\d{2}$/.test(input)) {
- return false;
- } else {
- return "请输入正确的日期格式,例如: 2025-09-01";
- }
- }
- // ==========================================
- // 业务流程函数
- // ==========================================
- // 1. 显示一个公告信息弹窗
- async function promptUserToStart() {
- try {
- console.log("即将显示公告弹窗...");
- const confirmed = await window.AndroidBridgePromise.showAlert(
- "重要通知",
- "导入前请确保您已成功登录教务系统,并选定正确的学期。",
- "好的,开始"
- );
- if (confirmed) {
- AndroidBridge.showToast("Alert:用户点击了确认!");
- return true;
- } else {
- AndroidBridge.showToast("Alert:用户取消了!");
- return false;
- }
- } catch (error) {
- AndroidBridge.showToast("Alert:显示弹窗出错!" + error.message);
- return false;
- }
- }
- // 2. 选择校区 (使用配置项)
- async function selectCampus() {
- try {
- // 从配置中提取用于展示的名称数组
- const campusLabels = CAMPUS_OPTIONS.map(opt => opt.label);
-
- const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
- "选择所在校区",
- JSON.stringify(campusLabels),
- 0
- );
- if (selectedIndex !== null && selectedIndex >= 0) {
- const selectedCampus = CAMPUS_OPTIONS[selectedIndex];
- if (typeof AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("已选择: " + selectedCampus.label);
- }
- // 返回选中的校区 ID ("MainCampus" 或 "Karamay")
- return selectedCampus.id;
- } else {
- if (typeof AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("取消导入:未选择校区。");
- }
- return null;
- }
- } catch (error) {
- console.error("选择校区时发生错误:", error);
- return null;
- }
- }
- // 3. 获取学期信息
- async function getTermCode() {
- try {
- if (typeof AndroidBridge !== 'undefined') AndroidBridge.showToast("正在获取学期列表...");
- if (typeof $ === 'undefined' || !$.ajax) {
- throw new Error("未检测到 jQuery 环境,请确保在正确的课表页面执行。");
- }
- const termData = await new Promise((resolve, reject) => {
- $.ajax({
- type: 'get',
- dataType: 'json',
- url: '/gmis/default/bindterm',
- cache: false,
- success: function (data) { resolve(data); },
- error: function (xhr, status, error) { reject(new Error(`网络请求失败,状态码: ${xhr.status} ${error}`)); }
- });
- });
- if (!termData || termData.length === 0) {
- throw new Error("未能获取到有效的学期列表数据。");
- }
- const semesterTexts = [];
- const semesterValues = [];
- let defaultSelectedIndex = 0;
- termData.forEach((item, index) => {
- semesterTexts.push(item.termname);
- semesterValues.push(item.termcode);
- if (item.selected) {
- defaultSelectedIndex = index;
- }
- });
- const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
- "选择导入学期",
- JSON.stringify(semesterTexts),
- defaultSelectedIndex
- );
- if (selectedIndex !== null && selectedIndex >= 0) {
- const selectedValue = semesterValues[selectedIndex];
- if (typeof AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("已选择学期: " + semesterTexts[selectedIndex]);
- }
- return selectedValue;
- } else {
- if (typeof AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("取消导入:未选择学期。");
- }
- return null;
- }
- } catch (error) {
- console.error("读取学期信息时发生错误:", error);
- if (typeof AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("Alert:读取学期信息出错!" + error.message);
- }
- return null;
- }
- }
- // 4. 获取课程数据
- async function fetchData(termCode) {
- try {
- if (typeof $ === 'undefined' || !$.ajax) {
- throw new Error("未检测到 jQuery 环境,请确保在正确的课表页面执行。");
- }
- const response = await new Promise((resolve, reject) => {
- $.ajax({
- type: 'post',
- dataType: 'json',
- url: "../pygl/py_kbcx_ew",
- data: { 'kblx': 'xs', 'termcode': termCode },
- cache: false,
- success: function (data) { resolve(data); },
- error: function (xhr, status, error) { reject(new Error(`网络请求失败,状态码: ${xhr.status} ${error}`)); }
- });
- });
- if (!response || !response.rows) {
- throw new Error("接口返回数据为空或解密后格式不正确");
- }
- return response.rows;
- } catch (error) {
- AndroidBridge.showToast("Alert:获取数据出错!" + error.message);
- return null;
- }
- }
- // 5. 导入课程数据
- async function parseCourses(py_kbcx_ew, isKaramayCampus) {
- // 用于存放每一小节课的临时数组
- let allCourseBlocks = [];
- // 辅助函数 1:根据 jcid 转换成标准的节次编号
- function getStandardSection(jcid) {
- // 上午始终是 1 开始 (11-15 -> 1-5)
- if (jcid >= 11 && jcid <= 15) return jcid - 10;
-
- // 下午偏移量:克拉玛依上午有5节,所以下午从第6节开始(+5);本校上午只有4节,下午从第5节开始(+4)
- let afternoonOffset = isKaramayCampus ? 5 : 4;
- if (jcid >= 21 && jcid <= 24) return jcid - 20 + afternoonOffset;
-
- // 晚上偏移量:克拉玛依白天9节(5+4),晚上从10开始(+9);本校白天8节(4+4),晚上从9开始(+8)
- let eveningOffset = isKaramayCampus ? 9 : 8;
- if (jcid >= 31 && jcid <= 33) return jcid - 30 + eveningOffset;
-
- return 1; // 默认兜底
- }
- // 辅助函数 2:解析类似 "连续周 1-12周" 或 "单周 1-11周" 的字符串,返回数字数组
- function parseWeeks(weekStr) {
- let weeks = [];
- let isSingle = weekStr.includes('单');
- let isDouble = weekStr.includes('双');
- let matches = weekStr.match(/\d+-\d+|\d+/g);
- if (matches) {
- matches.forEach(m => {
- if (m.includes('-')) {
- let [start, end] = m.split('-').map(Number);
- for (let i = start; i <= end; i++) {
- if (isSingle && i % 2 === 0) continue;
- if (isDouble && i % 2 !== 0) continue;
- weeks.push(i);
- }
- } else {
- let w = Number(m);
- if (isSingle && w % 2 === 0) return;
- if (isDouble && w % 2 !== 0) return;
- weeks.push(w);
- }
- });
- }
- return [...new Set(weeks)].sort((a, b) => a - b);
- }
- // --- 第一步:将按“行”排列的数据,拆解提取出每一小节课 ---
- py_kbcx_ew.forEach(row => {
- // 本校区强行剔除上午第5节 (jcid === 15)
- if (!isKaramayCampus && row.jcid === 15) {
- return;
- }
- let currentSection = getStandardSection(row.jcid);
- // 遍历星期一 (z1) 到星期日 (z7)
- for (let day = 1; day <= 7; day++) {
- let zVal = row['z' + day];
- if (zVal) {
- // 如果同一个时间有两门课(比如单双周不同),按 <br/> 拆分
- let classParts = zVal.split(/<br\s*\/?>/i);
-
- classParts.forEach(part => {
- let match = part.match(/(.*?)\[(.*?)\]([^\[]*)(?:\[(.*?)\])?$/);
-
- if (match) {
- allCourseBlocks.push({
- name: match[1].trim(), // 提取:课程名
- weekStr: match[2].trim(), // 提取:原始周次字符串 (用于后续比对)
- weeks: parseWeeks(match[2]), // 解析:纯数字周次数组
- teacher: match[3] ? match[3].trim() : "",// 提取:老师
- position: match[4] ? match[4].trim() : "未知地点", // 提取:上课地点
- day: day, // 星期几
- section: currentSection // 当前是第几节
- });
- }
- });
- }
- }
- });
- // --- 第二步:将连续的小节课“合并”成一门完整的课 ---
- let mergedCourses = [];
- allCourseBlocks.forEach(block => {
- // 寻找是否已经有相邻的课可以合并 (同星期、同课名、同老师、同地点、同周次,且节次刚好挨着)
- let existingCourse = mergedCourses.find(c =>
- c.day === block.day &&
- c.name === block.name &&
- c.teacher === block.teacher &&
- c.position === block.position &&
- c.weekStr === block.weekStr &&
- c.endSection === block.section - 1 // 核心:判断是否紧挨着上一节
- );
- if (existingCourse) {
- // 如果可以合并,就把结束节次往后延
- existingCourse.endSection = block.section;
- } else {
- // 如果不能合并,就作为一门新课加入
- mergedCourses.push({
- name: block.name,
- teacher: block.teacher,
- position: block.position,
- day: block.day,
- startSection: block.section,
- endSection: block.section,
- weeks: block.weeks,
- weekStr: block.weekStr
- });
- }
- });
- // 清理掉多余的辅助比对字段,输出最终给拾光 App 的标准格式
- const finalCourses = mergedCourses.map(c => {
- delete c.weekStr;
- return c;
- });
- try {
- const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(finalCourses));
- if (result === true) {
- if (typeof AndroidBridge !== 'undefined') AndroidBridge.showToast("测试课程导入成功!");
- } else {
- if (typeof AndroidBridge !== 'undefined') AndroidBridge.showToast("测试课程导入失败,请查看日志。");
- }
- } catch (error) {
- if (typeof AndroidBridge !== 'undefined') AndroidBridge.showToast("导入课程失败: " + error.message);
- }
- }
- // 6. 导入预设时间段
- async function importPresetTimeSlots(campusId) {
- const campusConfig = PRESET_TIME_CONFIG.campuses[campusId];
- const commonConfig = PRESET_TIME_CONFIG.common;
- const generatedSlots = [];
- let currentSectionNum = 1;
- // 辅助函数:把 HH:mm 转换成分钟数 (例如 08:00 -> 480)
- function timeToMinutes(timeStr) {
- const [h, m] = timeStr.split(':').map(Number);
- return h * 60 + m;
- }
- // 辅助函数:把分钟数转换成 HH:mm
- function minutesToTime(mins) {
- const h = Math.floor(mins / 60).toString().padStart(2, '0');
- const m = (mins % 60).toString().padStart(2, '0');
- return `${h}:${m}`;
- }
- // 按照上午、下午、晚上的顺序生成
- const periods = ["morning", "afternoon", "evening"];
-
- periods.forEach(period => {
- const count = campusConfig.sectionCounts[period];
- if (count === 0) return; // 如果该时段没课,跳过
- let currentMins = timeToMinutes(campusConfig.startTimes[period]);
- const longBreakPos = commonConfig.longBreakAfter[period];
- for (let i = 1; i <= count; i++) {
- const startStr = minutesToTime(currentMins);
- currentMins += campusConfig.durations.classMinutes; // 加上课时间
- const endStr = minutesToTime(currentMins);
- generatedSlots.push({
- number: currentSectionNum,
- startTime: startStr,
- endTime: endStr
- });
- currentSectionNum++;
- // 如果不是该时段的最后一节课,则加上课间休息时间,推算出下一节的开始时间
- if (i < count) {
- if (i === longBreakPos) {
- currentMins += campusConfig.durations.longBreakMinutes;
- } else {
- currentMins += campusConfig.durations.shortBreakMinutes;
- }
- }
- }
- });
- try {
- const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(generatedSlots));
- if (result === true) {
- window.AndroidBridge.showToast("测试时间段导入成功!");
- } else {
- window.AndroidBridge.showToast("测试时间段导入失败,请查看日志。");
- }
- } catch (error) {
- window.AndroidBridge.showToast("导入时间段失败: " + error.message);
- }
- }
- // 7. 导入课表配置
- async function saveConfig() {
- let startDate = await window.AndroidBridgePromise.showPrompt(
- "输入开学日期",
- "请输入本学期开学日期 (格式: YYYY-MM-DD):",
- "2025-09-01",
- "validateDateInput"
- );
- if (startDate === null) {
- if (typeof AndroidBridge !== 'undefined') {
- AndroidBridge.showToast("已取消开学日期设置,将使用默认配置。");
- }
- startDate = "2025-09-01";
- } else {
- startDate = startDate.trim().replace(/[\/\.]/g, '-');
- }
- const courseConfigData = {
- "semesterStartDate": startDate,
- "semesterTotalWeeks": 25,
- "defaultClassDuration": 45,
- "defaultBreakDuration": 5,
- "firstDayOfWeek": 1
- };
- try {
- const configJsonString = JSON.stringify(courseConfigData);
- const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
- if (result === true) {
- AndroidBridge.showToast("测试配置导入成功!开学日期: " + startDate);
- } else {
- AndroidBridge.showToast("测试配置导入失败,请查看日志。");
- }
- } catch (error) {
- AndroidBridge.showToast("导入配置失败: " + error.message);
- }
- }
- /**
- * 编排整个课程导入流程。
- */
- async function runImportFlow() {
- // 1. 公告和前置检查。
- const alertConfirmed = await promptUserToStart();
- if (!alertConfirmed) return;
-
- // 2. 选择校区。 (获取校区ID)
- const campusId = await selectCampus();
- if (campusId === null) return;
-
- // 生成一个 boolean 给解析课程使用
- const isKaramayCampus = (campusId === "Karamay");
- // 3. 获取学期。
- const termCode = await getTermCode();
- if (termCode === null) {
- AndroidBridge.showToast("导入已取消。");
- return;
- }
- // 4. 获取课程数据
- const py_kbcx_ew = await fetchData(termCode);
- if (py_kbcx_ew === null) {
- AndroidBridge.showToast("导入已取消。");
- return;
- }
- // 5. 解析课程信息。 (传入 boolean)
- await parseCourses(py_kbcx_ew, isKaramayCampus);
-
- // 6. 导入时间段数据。 (传入字符串 campusId,供引擎推算)
- await importPresetTimeSlots(campusId);
-
- // 7. 保存配置数据
- await saveConfig();
- // 8. 流程**完全成功**,发送结束信号。
- AndroidBridge.notifyTaskCompletion();
- }
- // 启动所有演示
- runImportFlow();
|