| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // 铜仁学院移动教务 (gztrc.edu.cn:17230/yjw) 拾光课程表适配脚本
- // 使用个人课表接口(courseSchedule)查询
- // 登录后需要从 localStorage 中读取用户信息,无需在脚本内登录
- const API = {
- semesters: "/api/baseInfo/mobile/common/querySemester",
- weeks: "/api/baseInfo/mobile/common/queryCurrentSemesterWeekList",
- times: "/api/baseInfo/mobile/common/timeNameDetail",
- schedule: "/api/arrange/mobile/courseSchedule/courseSchedule"
- };
- async function apiGet(path) {
- const res = await fetch(path, { credentials: "include" });
- if (!res.ok) throw new Error("请求失败: " + path);
- return res.json();
- }
- async function apiPost(path, data, token) {
- const res = await fetch(path, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "accessToken": token
- },
- credentials: "include",
- body: JSON.stringify(data)
- });
- if (!res.ok) throw new Error("请求失败: " + path);
- return res.json();
- }
- function parseUserFromStorage() {
- try {
- const raw = localStorage.getItem("user_info");
- if (!raw) return null;
- const data = JSON.parse(raw);
- if (!data || !data.accessToken || !data.userInfo || !data.userInfo.userId) return null;
- return {
- token: data.accessToken,
- userId: data.userInfo.userId,
- userType: data.userInfo.userType || "0",
- name: data.userInfo.name || ""
- };
- } catch (e) {
- return null;
- }
- }
- /**
- * 解析单条课表记录(接口按"天 x 节次"逐格返回,每格一条记录)
- * week 为空时使用记录自带的 weeks 字段
- */
- function parseCourseItem(c, week) {
- if (!c || !c.courseName) return null;
- const section = parseInt(c.time);
- if (isNaN(section) || section < 1) return null;
- let w = week;
- if (w === undefined || w === null) {
- w = parseInt(c.weeks);
- if (isNaN(w) || w < 1) return null;
- }
- // 教务dayOfWeek: 1=周日,2=周一,3=周二,4=周三,5=周四,6=周五,7=周六
- // 规范day: 1=周一,2=周二,3=周三,4=周四,5=周五,6=周六,7=周日
- const dayMap = { "1": 7, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6 };
- const day = dayMap[String(c.dayOfWeek)] || parseInt(c.dayOfWeek);
- let position = c.classroomName;
- if (!position || position === "," || position.trim() === "") {
- position = "未知地点";
- }
- return {
- name: c.courseName,
- teacher: c.teacherName || "",
- position: position,
- day: day,
- section: section,
- week: w
- };
- }
- function sameWeekSet(a, b) {
- if (a.length !== b.length) return false;
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) return false;
- }
- return true;
- }
- function pushWeek(list, w) {
- if (list.indexOf(w) === -1) list.push(w);
- }
- /**
- * 将逐格记录合并为课程:
- * 先按(天,课程,教师,地点,节次)聚合周次,再合并连续节次(且周次集合相同)
- */
- function mergeToCourses(records) {
- const groups = new Map();
- for (let r of records) {
- const key = [r.day, r.name, r.teacher, r.position].join("|");
- let g = groups.get(key);
- if (!g) {
- g = { day: r.day, name: r.name, teacher: r.teacher, position: r.position, secMap: new Map() };
- groups.set(key, g);
- }
- if (!g.secMap.has(r.section)) g.secMap.set(r.section, []);
- pushWeek(g.secMap.get(r.section), r.week);
- }
- const courses = [];
- for (let g of groups.values()) {
- const sections = Array.from(g.secMap.keys()).sort((a, b) => a - b);
- let cur = null;
- for (let sec of sections) {
- const weeks = g.secMap.get(sec).sort((a, b) => a - b);
- if (cur && sec === cur.endSection + 1 && sameWeekSet(cur.weeks, weeks)) {
- cur.endSection = sec;
- } else {
- cur = {
- name: g.name,
- teacher: g.teacher,
- position: g.position,
- day: g.day,
- startSection: sec,
- endSection: sec,
- weeks: weeks.slice()
- };
- courses.push(cur);
- }
- }
- }
- return courses;
- }
- async function querySchedule(user, semesterId, weeks) {
- const body = {
- academicYearSemester: semesterId,
- userId: user.userId,
- userType: user.userType,
- weeks: weeks
- };
- const json = await apiPost(API.schedule, body, user.token);
- if (json.code !== 200) {
- if (json.code === 53000505) {
- throw new Error("登录已失效,请重新登录教务系统后重试。");
- }
- throw new Error(json.message || "获取课表失败");
- }
- if (!json.data || !json.data.course) throw new Error("课表数据为空");
- return json.data.course;
- }
- async function fetchAllCourses(user, semesterId, totalWeeks) {
- // 注意:一次请求多周时,接口会把同一格子的多周数据以逗号拼接返回
- // (如 courseName:"课A,课B"、teacherName:"师A,师A,..."),无法可靠拆分,
- // 因此必须逐周查询,单周返回的数据是干净的
- const records = [];
- for (let w = 1; w <= totalWeeks; w++) {
- const list = await querySchedule(user, semesterId, [w]);
- records.push(...list.map(c => parseCourseItem(c, w)).filter(c => c !== null));
- }
- return mergeToCourses(records);
- }
- async function fetchTimeSlots() {
- const json = await apiGet(API.times);
- if (json.code !== 200 || !json.data) throw new Error("获取作息时间失败");
- return json.data.map(t => ({
- number: parseInt(t.timeCode),
- startTime: t.startTime,
- endTime: t.endTime
- }));
- }
- async function runImportFlow() {
- try {
- window.shiguangBridge.showToast("开始导入课表...");
- // 1. 读取登录信息
- const user = parseUserFromStorage();
- if (!user) {
- await window.shiguangBridgePromise.showAlert(
- "请先登录",
- "请在教务系统(移动教务)中完成登录,进入主页后再次点击执行导入。",
- "知道了"
- );
- return;
- }
- // 2. 获取学期列表
- window.shiguangBridge.showToast("正在获取学期列表...");
- const semesterJson = await apiGet(API.semesters);
- if (semesterJson.code !== 200) throw new Error("获取学期列表失败");
- const semesters = (semesterJson.data || []).filter(s => s && s.semesterId && /^\d{4}-\d{4}-\d+$/.test(s.semesterId));
- if (semesters.length === 0) throw new Error("未能获取学期列表");
- // 3. 选择学期
- let defaultIndex = 0;
- const currentIdx = semesters.findIndex(s => s.isCurrentSemester === "1");
- if (currentIdx !== -1) defaultIndex = currentIdx;
- const semesterIndex = await window.shiguangBridgePromise.showSingleSelection(
- "选择学期",
- JSON.stringify(semesters.map(s => s.semesterName)),
- defaultIndex
- );
- if (semesterIndex === null) {
- window.shiguangBridge.showToast("导入已取消。");
- return;
- }
- const semester = semesters[semesterIndex];
- // 4. 获取本学期周数
- let totalWeeks = 20;
- try {
- const weekJson = await apiGet(API.weeks);
- if (weekJson.code === 200 && weekJson.data && weekJson.data.length > 0) {
- totalWeeks = weekJson.data.length;
- }
- } catch (e) {
- // 使用默认周数
- }
- // 5. 获取课表数据
- window.shiguangBridge.showToast("正在获取课表数据,共 " + totalWeeks + " 周...");
- const courses = await fetchAllCourses(user, semester.semesterId, totalWeeks);
- if (courses.length === 0) throw new Error("未解析到课程数据,可能该学期暂无课表。");
- // 6. 保存预设时间段
- try {
- const timeSlots = await fetchTimeSlots();
- if (timeSlots.length > 0) {
- await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- }
- } catch (e) {
- window.shiguangBridge.showToast("时间段导入失败,但课程将继续导入。");
- }
- // 7. 保存课程数据
- await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
- window.shiguangBridge.showToast("成功导入 " + courses.length + " 条课程记录!");
- window.shiguangBridge.notifyTaskCompletion();
- } catch (e) {
- console.error("[适配脚本错误] " + e.message);
- window.shiguangBridge.showToast("导入失败: " + e.message);
- }
- }
- runImportFlow();
|