|
|
@@ -0,0 +1,244 @@
|
|
|
+// 文件: guilin_medical.js
|
|
|
+// 功能:从桂医教务系统获取课程表,通过桥接 API 导入到拾光课程表
|
|
|
+
|
|
|
+// ---------- 全局验证函数 ----------
|
|
|
+function validateYearInput(input) {
|
|
|
+ if (/^\d{4}$/.test(input)) {
|
|
|
+ return false; // 验证通过
|
|
|
+ } else {
|
|
|
+ return "请输入四位数字的年份(例如 2024)";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ---------- 工具函数 ----------
|
|
|
+function parseWeeks(weeksStr) {
|
|
|
+ weeksStr = weeksStr.replace('周', '');
|
|
|
+ const parts = weeksStr.split(',');
|
|
|
+ const weeks = [];
|
|
|
+ for (const part of parts) {
|
|
|
+ if (part.includes('-')) {
|
|
|
+ const [start, end] = part.split('-').map(Number);
|
|
|
+ for (let i = start; i <= end; i++) weeks.push(i);
|
|
|
+ } else {
|
|
|
+ weeks.push(Number(part));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return weeks;
|
|
|
+}
|
|
|
+
|
|
|
+function parseSections(jcdm) {
|
|
|
+ const str = String(jcdm);
|
|
|
+ const sections = [];
|
|
|
+ if (str.includes('-')) {
|
|
|
+ const [start, end] = str.split('-').map(Number);
|
|
|
+ for (let i = start; i <= end; i++) sections.push(i);
|
|
|
+ } else if (/^\d+$/.test(str)) {
|
|
|
+ for (let i = 0; i < str.length; i += 2) {
|
|
|
+ const sec = parseInt(str.substring(i, i + 2), 10);
|
|
|
+ if (!isNaN(sec)) sections.push(sec);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sections.push(parseInt(str));
|
|
|
+ }
|
|
|
+ return sections;
|
|
|
+}
|
|
|
+
|
|
|
+function parseRawCourses(rawData) {
|
|
|
+ const courseInfos = [];
|
|
|
+ for (const course of rawData) {
|
|
|
+ const name = course.kcmc;
|
|
|
+ const teacher = course.teaxms;
|
|
|
+ const position = course.jxcdmc;
|
|
|
+ const weeks = parseWeeks(course.zc);
|
|
|
+ const day = parseInt(course.xq);
|
|
|
+ const sections = parseSections(course.jcdm);
|
|
|
+ courseInfos.push({ name, teacher, position, weeks, day, sections });
|
|
|
+ }
|
|
|
+ return courseInfos;
|
|
|
+}
|
|
|
+
|
|
|
+function convertToTargetCourses(middleCourses) {
|
|
|
+ return middleCourses.map(c => ({
|
|
|
+ name: c.name,
|
|
|
+ teacher: c.teacher,
|
|
|
+ position: c.position,
|
|
|
+ day: c.day,
|
|
|
+ startSection: c.sections[0],
|
|
|
+ endSection: c.sections[c.sections.length - 1],
|
|
|
+ weeks: c.weeks,
|
|
|
+ isCustomTime: false
|
|
|
+ }));
|
|
|
+}
|
|
|
+
|
|
|
+function getTimeSlots() {
|
|
|
+ return [
|
|
|
+ { number: 1, startTime: "08:30", endTime: "09:10" },
|
|
|
+ { number: 2, startTime: "09:20", endTime: "10:00" },
|
|
|
+ { number: 3, startTime: "10:10", endTime: "10:50" },
|
|
|
+ { number: 4, startTime: "11:00", endTime: "11:40" },
|
|
|
+ { number: 5, startTime: "11:50", endTime: "12:30" },
|
|
|
+ { number: 6, startTime: "14:30", endTime: "15:10" },
|
|
|
+ { number: 7, startTime: "15:20", endTime: "16:00" },
|
|
|
+ { number: 8, startTime: "16:10", endTime: "16:50" },
|
|
|
+ { number: 9, startTime: "17:00", endTime: "17:40" },
|
|
|
+ { number: 10, startTime: "19:00", endTime: "19:40" },
|
|
|
+ { number: 11, startTime: "19:50", endTime: "20:30" },
|
|
|
+ { number: 12, startTime: "20:40", endTime: "21:20" }
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+// ---------- 网络请求 ----------
|
|
|
+async function fetchCourseData(xnxqdm) {
|
|
|
+ let page = 1;
|
|
|
+ const rowsPerPage = 100;
|
|
|
+ let allRows = [];
|
|
|
+ let total = 0;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ const res = await fetch("https://ejwc.glmu.edu.cn/xsgrkbcx!getDataList.action", {
|
|
|
+ headers: {
|
|
|
+ "accept": "application/json, text/javascript, */*; q=0.01",
|
|
|
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
+ "x-requested-with": "XMLHttpRequest"
|
|
|
+ },
|
|
|
+ body: `xnxqdm=${xnxqdm}&page=${page}&rows=${rowsPerPage}`,
|
|
|
+ method: "POST",
|
|
|
+ credentials: "include"
|
|
|
+ });
|
|
|
+ if (!res.ok) {
|
|
|
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
|
+ }
|
|
|
+ const ret = await res.json();
|
|
|
+ if (!ret.rows || !Array.isArray(ret.rows)) {
|
|
|
+ throw new Error("返回数据格式不正确");
|
|
|
+ }
|
|
|
+ total = ret.total;
|
|
|
+ allRows = allRows.concat(ret.rows);
|
|
|
+ if (allRows.length >= total) break;
|
|
|
+ page++;
|
|
|
+ if (page > 10) break;
|
|
|
+ }
|
|
|
+ return allRows;
|
|
|
+}
|
|
|
+
|
|
|
+// ---------- 用户交互 ----------
|
|
|
+async function promptUserToStart() {
|
|
|
+ return await window.AndroidBridgePromise.showAlert(
|
|
|
+ "重要提醒",
|
|
|
+ "请确保您已登录桂医教务系统,且当前页面为教务系统内任意页面。\n\n点击确定继续。",
|
|
|
+ "确定"
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+async function getAcademicYear() {
|
|
|
+ return await window.AndroidBridgePromise.showPrompt(
|
|
|
+ "学年设置",
|
|
|
+ "请输入本学年开始的年份(例如 2024)",
|
|
|
+ "2024",
|
|
|
+ "validateYearInput" // 传入验证函数名
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+async function selectSemester() {
|
|
|
+ const semesterOptions = ["上学期", "下学期", "短学期"];
|
|
|
+ const index = await window.AndroidBridgePromise.showSingleSelection(
|
|
|
+ "选择学期",
|
|
|
+ JSON.stringify(semesterOptions),
|
|
|
+ 0
|
|
|
+ );
|
|
|
+ if (index === null || index < 0) return null;
|
|
|
+ return index;
|
|
|
+}
|
|
|
+
|
|
|
+// ---------- 主流程 ----------
|
|
|
+async function run() {
|
|
|
+ try {
|
|
|
+ // 1. 公告
|
|
|
+ const confirmed = await promptUserToStart();
|
|
|
+ if (!confirmed) {
|
|
|
+ AndroidBridge.showToast("用户取消了导入流程。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取学年
|
|
|
+ const yearInput = await getAcademicYear();
|
|
|
+ if (yearInput === null) {
|
|
|
+ AndroidBridge.showToast("导入已取消。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const yearNum = parseInt(yearInput);
|
|
|
+ if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
|
|
|
+ await window.AndroidBridgePromise.showAlert("错误", "学年输入无效,请输入2000-2100之间的数字。", "确定");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 获取学期
|
|
|
+ const semesterIndex = await selectSemester();
|
|
|
+ if (semesterIndex === null) {
|
|
|
+ AndroidBridge.showToast("导入已取消。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const termCode = semesterIndex === 0 ? "01" : (semesterIndex === 1 ? "02" : "03");
|
|
|
+ const xnxqdm = `${yearNum}${termCode}`;
|
|
|
+
|
|
|
+ // 4. 请求课表
|
|
|
+ AndroidBridge.showToast("正在获取课表,请稍候...");
|
|
|
+ let rawData;
|
|
|
+ try {
|
|
|
+ rawData = await fetchCourseData(xnxqdm);
|
|
|
+ } catch (fetchErr) {
|
|
|
+ await window.AndroidBridgePromise.showAlert(
|
|
|
+ "网络请求失败",
|
|
|
+ `请求教务系统失败:${fetchErr.message}\n\n请检查网络连接和登录状态。`,
|
|
|
+ "确定"
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!rawData.length) {
|
|
|
+ await window.AndroidBridgePromise.showAlert("提示", "未获取到任何课程数据。请确认已登录教务系统并选择正确的学年学期。", "确定");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解析并转换
|
|
|
+ const middleCourses = parseRawCourses(rawData);
|
|
|
+ const targetCourses = convertToTargetCourses(middleCourses);
|
|
|
+
|
|
|
+ // 6. 保存课程
|
|
|
+ try {
|
|
|
+ await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(targetCourses));
|
|
|
+ AndroidBridge.showToast(`课程数据已导入(共 ${targetCourses.length} 条)`);
|
|
|
+ } catch (saveErr) {
|
|
|
+ await window.AndroidBridgePromise.showAlert("保存课程失败", saveErr.message, "确定");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 保存时间段
|
|
|
+ const timeSlots = getTimeSlots();
|
|
|
+ try {
|
|
|
+ await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
|
|
|
+ AndroidBridge.showToast("时间段数据已导入");
|
|
|
+ } catch (slotErr) {
|
|
|
+ // 时间段保存失败不终止流程,只提示
|
|
|
+ AndroidBridge.showToast(`时间段保存失败:${slotErr.message}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 完成通知
|
|
|
+ AndroidBridge.showToast("导入完成!");
|
|
|
+ AndroidBridge.notifyTaskCompletion();
|
|
|
+
|
|
|
+ } catch (err) {
|
|
|
+ // 捕获所有未预料的错误
|
|
|
+ console.error("run error:", err);
|
|
|
+ await window.AndroidBridgePromise.showAlert(
|
|
|
+ "导入失败",
|
|
|
+ `未知错误:${err.message || err}\n\n请联系开发者。`,
|
|
|
+ "确定"
|
|
|
+ );
|
|
|
+ // 仍然通知完成,但可能不会生成有效文件
|
|
|
+ AndroidBridge.notifyTaskCompletion();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 启动
|
|
|
+run();
|