| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- // 西北工业大学(NWPU) 拾光课程表适配脚本
- // 适配系统:EAMS 教务系统(jwxt.nwpu.edu.cn)
- // 适配范围:本科(BACHELOR_AND_ASSOCIATE)
- // 维护者:用户7227
- /**
- * 将 HHmm 整数格式转换为 HH:mm 字符串
- * @param {number} hhmm - 如 830, 915, 1400
- * @returns {string} - 如 "08:30", "09:15", "14:00"
- */
- function formatTime(hhmm) {
- var h = String(Math.floor(hhmm / 100));
- var m = String(hhmm % 100);
- if (h.length < 2) h = '0' + h;
- if (m.length < 2) m = '0' + m;
- return h + ':' + m;
- }
- /**
- * 发起网络请求
- * @param {string} url - 请求地址
- * @returns {Promise<string>} - 响应文本
- */
- async function request(url) {
- var res = await fetch(url, { credentials: "include" });
- if (!res.ok) throw new Error("网络请求失败: " + res.status);
- return await res.text();
- }
- /**
- * 获取学生 ID
- * 调用 student-portrait API 获取当前登录学生的数据库 ID
- * @returns {Promise<number|null>} - 学生 ID
- */
- async function getStudentId() {
- console.log("[NWPU] 正在获取学生 ID...");
- try {
- var data = await request("/student/for-std/student-portrait/getStdInfo?bizTypeAssoc=2&cultivateTypeAssoc=1");
- var json = JSON.parse(data);
- if (json && json.student && json.student.id) {
- console.log("[NWPU] 学生 ID: " + json.student.id);
- return json.student.id;
- }
- console.error("[NWPU] API 响应中未找到 student.id");
- return null;
- } catch (e) {
- console.error("[NWPU] 获取学生 ID 失败: " + e.message);
- return null;
- }
- }
- /**
- * 获取学期列表
- * 请求课表页面 HTML,解析 #allSemesters 下拉框
- * @returns {Promise<Array<{id: number, name: string}>>} - 学期列表
- */
- async function getSemesters() {
- console.log("[NWPU] 正在获取学期列表...");
- try {
- var html = await request("/student/for-std/course-table");
- var parser = new DOMParser();
- var doc = parser.parseFromString(html, "text/html");
- var select = doc.querySelector("#allSemesters");
- if (!select) {
- console.error("[NWPU] 未找到 #allSemesters 元素");
- return [];
- }
- var options = select.querySelectorAll("option");
- var semesters = [];
- for (var i = 0; i < options.length; i++) {
- var opt = options[i];
- var id = parseInt(opt.getAttribute("value"));
- var name = opt.textContent.trim();
- if (!isNaN(id) && name) {
- semesters.push({ id: id, name: name });
- }
- }
- console.log("[NWPU] 获取到 " + semesters.length + " 个学期");
- return semesters;
- } catch (e) {
- console.error("[NWPU] 获取学期列表失败: " + e.message);
- return [];
- }
- }
- /**
- * 获取课程数据
- * 调用 print-data API 获取指定学期的课程和时间表信息
- * @param {number} semesterId - 学期 ID
- * @param {number} studentId - 学生 ID
- * @returns {Promise<object|null>} - API 响应 JSON
- */
- async function getCourseData(semesterId, studentId) {
- console.log("[NWPU] 正在获取课程数据...");
- AndroidBridge.showToast("正在同步课表...");
- try {
- var url = "/student/for-std/course-table/semester/" + semesterId + "/print-data/" + studentId;
- var data = await request(url);
- return JSON.parse(data);
- } catch (e) {
- console.error("[NWPU] 获取课程数据失败: " + e.message);
- AndroidBridge.showToast("获取课程数据失败: " + e.message);
- return null;
- }
- }
- /**
- * NWPU 默认作息时间(长安校区)
- * 当 API 响应中不包含 timeTableLayout 时使用
- */
- function getDefaultTimeSlots() {
- return [
- { number: 1, startTime: "08:30", endTime: "09:15" },
- { number: 2, startTime: "09:25", endTime: "10:10" },
- { number: 3, startTime: "10:30", endTime: "11:15" },
- { number: 4, startTime: "11:25", endTime: "12:10" },
- { number: 5, startTime: "12:20", endTime: "13:05" },
- { number: 6, startTime: "13:05", endTime: "13:50" },
- { number: 7, startTime: "14:00", endTime: "14:45" },
- { number: 8, startTime: "14:55", endTime: "15:40" },
- { number: 9, startTime: "16:00", endTime: "16:45" },
- { number: 10, startTime: "16:55", endTime: "17:40" },
- { number: 11, startTime: "19:00", endTime: "19:45" },
- { number: 12, startTime: "19:55", endTime: "20:40" },
- { number: 13, startTime: "20:40", endTime: "21:25" }
- ];
- }
- /**
- * 从 API 响应中提取时间段
- * 优先从 timeTableLayout.courseUnitList 动态获取,否则使用默认时间段
- * @param {object} apiData - API 响应
- * @returns {Array<{number: number, startTime: string, endTime: string}>}
- */
- function extractTimeSlots(apiData) {
- var layout = apiData.timeTableLayout;
- if (layout && layout.courseUnitList) {
- var slots = [];
- for (var i = 0; i < layout.courseUnitList.length; i++) {
- var unit = layout.courseUnitList[i];
- slots.push({
- number: unit.indexNo,
- startTime: formatTime(unit.startTime),
- endTime: formatTime(unit.endTime)
- });
- }
- console.log("[NWPU] 从 API 提取到 " + slots.length + " 个时间段");
- return slots;
- }
- console.log("[NWPU] API 未返回时间段,使用默认作息时间");
- return getDefaultTimeSlots();
- }
- /**
- * 获取学期信息(开始日期、结束日期)
- * 调用 ws/semester/get API
- * @param {number} semesterId - 学期 ID
- * @returns {Promise<{startDate: string|null, endDate: string|null}>}
- */
- async function getSemesterInfo(semesterId) {
- try {
- var data = await request("/student/ws/semester/get/" + semesterId);
- var json = JSON.parse(data);
- return {
- startDate: json.startDate || null,
- endDate: json.endDate || null
- };
- } catch (e) {
- console.warn("[NWPU] 获取学期信息失败: " + e.message);
- return { startDate: null, endDate: null };
- }
- }
- /**
- * 计算两个日期之间的周数
- * @param {string} startDate - YYYY-MM-DD
- * @param {string} endDate - YYYY-MM-DD
- * @returns {number}
- */
- function calculateTotalWeeks(startDate, endDate) {
- if (!startDate || !endDate) return 20;
- var start = new Date(startDate);
- var end = new Date(endDate);
- var diffMs = end.getTime() - start.getTime();
- var diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
- return Math.ceil(diffDays / 7);
- }
- /**
- * 从 API 响应和学期信息中提取学期配置
- * @param {object} semesterInfo - 学期信息 {startDate, endDate}
- * @param {Array} activities - 课程活动列表
- * @returns {object} - CourseConfigJsonModel
- */
- function extractCourseConfig(semesterInfo, activities) {
- var semesterStartDate = semesterInfo.startDate;
- var semesterTotalWeeks = 20;
- // 优先从学期日期计算总周数
- if (semesterInfo.startDate && semesterInfo.endDate) {
- semesterTotalWeeks = calculateTotalWeeks(semesterInfo.startDate, semesterInfo.endDate);
- } else if (activities.length > 0) {
- // 回退:从 weekIndexes 推算
- var maxWeek = 0;
- for (var i = 0; i < activities.length; i++) {
- var weeks = activities[i].weekIndexes;
- if (weeks && weeks.length > 0) {
- for (var j = 0; j < weeks.length; j++) {
- if (weeks[j] > maxWeek) maxWeek = weeks[j];
- }
- }
- }
- if (maxWeek > 0) semesterTotalWeeks = maxWeek;
- }
- console.log("[NWPU] 学期开始日期: " + (semesterStartDate || "未知") + ", 总周数: " + semesterTotalWeeks);
- return {
- semesterStartDate: semesterStartDate,
- semesterTotalWeeks: semesterTotalWeeks,
- defaultClassDuration: 45,
- defaultBreakDuration: 10,
- firstDayOfWeek: 1
- };
- }
- /**
- * 将 API 活动数据转换为 ImportCourseJsonModel 格式
- * @param {Array} activities - API activities 数组
- * @returns {Array<object>} - 课程列表
- */
- function convertCourses(activities) {
- var courses = [];
- for (var i = 0; i < activities.length; i++) {
- var act = activities[i];
- // 拼接教室信息:优先 room,辅以 building
- var position = act.room || act.building || "未知地点";
- // 取第一个教师
- var teacher = "未知教师";
- if (act.teachers && act.teachers.length > 0) {
- teacher = act.teachers[0];
- }
- // 排序周次
- var weeks = (act.weekIndexes || []).slice().sort(function(a, b) { return a - b; });
- if (act.courseName && weeks.length > 0) {
- courses.push({
- name: act.courseName,
- teacher: teacher,
- position: position,
- day: act.weekday,
- startSection: act.startUnit,
- endSection: act.endUnit,
- weeks: weeks
- });
- }
- }
- console.log("[NWPU] 转换完成,共 " + courses.length + " 个课程条目");
- return courses;
- }
- /**
- * 第一步:保存时间段
- */
- async function saveTimeSlots(timeSlots) {
- if (timeSlots.length === 0) {
- console.warn("[NWPU] 无时间段数据,跳过");
- return true;
- }
- try {
- console.log("[NWPU] 正在保存时间段...");
- await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- AndroidBridge.showToast("成功导入 " + timeSlots.length + " 个时间段");
- return true;
- } catch (e) {
- console.error("[NWPU] 保存时间段失败: " + e.message);
- AndroidBridge.showToast("保存时间段失败: " + e.message);
- return false;
- }
- }
- /**
- * 第二步:保存课表配置
- */
- async function saveConfig(config) {
- try {
- console.log("[NWPU] 正在保存课表配置...");
- await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
- AndroidBridge.showToast("课表配置导入成功");
- return true;
- } catch (e) {
- console.error("[NWPU] 保存配置失败: " + e.message);
- AndroidBridge.showToast("保存配置失败: " + e.message);
- return false;
- }
- }
- /**
- * 第三步:保存课程数据
- */
- async function saveCourses(courses) {
- if (courses.length === 0) {
- AndroidBridge.showToast("没有课程数据需要导入");
- return true;
- }
- try {
- console.log("[NWPU] 正在保存课程数据...");
- await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
- AndroidBridge.showToast("成功导入 " + courses.length + " 个课程条目");
- return true;
- } catch (e) {
- console.error("[NWPU] 保存课程失败: " + e.message);
- AndroidBridge.showToast("保存课程失败: " + e.message);
- return false;
- }
- }
- /**
- * 主导入流程
- */
- async function runImportFlow() {
- try {
- // 1. 显示导入说明
- var confirmed = await window.AndroidBridgePromise.showAlert(
- "西北工业大学课表导入",
- "导入前请确保您已在浏览器中登录教务系统(jwxt.nwpu.edu.cn)。\n\n" +
- "本适配将自动获取学期信息、时间段和课程数据。",
- "开始导入"
- );
- if (!confirmed) {
- AndroidBridge.showToast("导入已取消");
- return;
- }
- // 2. 获取学生 ID
- AndroidBridge.showToast("正在获取学生信息...");
- var studentId = await getStudentId();
- if (!studentId) {
- await window.AndroidBridgePromise.showAlert(
- "导入失败",
- "未能获取学生 ID,请确认您已登录教务系统。",
- "确定"
- );
- return;
- }
- // 3. 获取学期列表
- AndroidBridge.showToast("正在获取学期列表...");
- var semesters = await getSemesters();
- if (semesters.length === 0) {
- await window.AndroidBridgePromise.showAlert(
- "导入失败",
- "未能获取学期列表,请刷新页面后重试。",
- "确定"
- );
- return;
- }
- // 4. 用户选择学期
- var semesterNames = [];
- for (var i = 0; i < semesters.length; i++) {
- semesterNames.push(semesters[i].name);
- }
- var selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
- "选择学期",
- JSON.stringify(semesterNames),
- 0
- );
- if (selectedIndex === null || selectedIndex < 0 || selectedIndex >= semesters.length) {
- AndroidBridge.showToast("导入已取消");
- return;
- }
- var selectedSemester = semesters[selectedIndex];
- console.log("[NWPU] 选择学期: " + selectedSemester.name + " (ID: " + selectedSemester.id + ")");
- // 5. 获取课程数据
- var apiData = await getCourseData(selectedSemester.id, studentId);
- if (!apiData) return;
- var tableVm = apiData.studentTableVm;
- if (!tableVm || !tableVm.activities || tableVm.activities.length === 0) {
- AndroidBridge.showToast("该学期没有课程数据");
- return;
- }
- var activities = tableVm.activities;
- console.log("[NWPU] 获取到 " + activities.length + " 条课程活动");
- // 6. 获取学期信息(开始日期、结束日期)
- AndroidBridge.showToast("正在获取学期信息...");
- var semesterInfo = await getSemesterInfo(selectedSemester.id);
- console.log("[NWPU] 学期开始: " + (semesterInfo.startDate || "未知") + ", 结束: " + (semesterInfo.endDate || "未知"));
- // 7. 提取时间段
- var timeSlots = extractTimeSlots(apiData);
- // 8. 提取课表配置
- var courseConfig = extractCourseConfig(semesterInfo, activities);
- // 9. 转换课程数据
- var courses = convertCourses(activities);
- // 10. 三步导入:时间段 → 配置 → 课程
- var timeSlotResult = await saveTimeSlots(timeSlots);
- if (!timeSlotResult) return;
- var configResult = await saveConfig(courseConfig);
- if (!configResult) return;
- var courseResult = await saveCourses(courses);
- if (!courseResult) return;
- // 完成
- AndroidBridge.showToast("课表导入完成!");
- AndroidBridge.notifyTaskCompletion();
- } catch (e) {
- console.error("[NWPU] 导入异常: " + e.message);
- AndroidBridge.showToast("导入异常: " + e.message);
- }
- }
- // 启动导入流程
- runImportFlow();
|