| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059 |
- // ===================== 工具函数 =====================
- /**
- * 节次与周次合并去重函数
- * @param {Array<Object>} courses 原始解析课程数组
- * @returns {Array<Object>} 合并去重后的课程数组
- */
- function mergeAndDistinctCourses(courses) {
- if (!Array.isArray(courses) || courses.length <= 1) return courses;
- const list = courses.map(c => ({
- ...c,
- name: c.name || '',
- teacher: c.teacher || '',
- position: c.position || '',
- weeks: Array.isArray(c.weeks) ? [...c.weeks].sort((a, b) => a - b) : []
- }));
- list.sort((a, b) => {
- return a.name.localeCompare(b.name) ||
- a.teacher.localeCompare(b.teacher) ||
- a.position.localeCompare(b.position) ||
- (a.day || 0) - (b.day || 0) ||
- a.weeks.join(',').localeCompare(b.weeks.join(',')) ||
- (a.startSection || 0) - (b.startSection || 0);
- });
- const step1Merged = [];
- let current = list[0];
- for (let i = 1; i < list.length; i++) {
- const next = list[i];
- const isSameCourseAndWeeks =
- current.name === next.name &&
- current.teacher === next.teacher &&
- current.position === next.position &&
- current.day === next.day &&
- current.weeks.join(',') === next.weeks.join(',');
- const isContinuous = current.endSection + 1 === next.startSection;
- const isDuplicate = current.startSection === next.startSection && current.endSection === next.endSection;
- if (isSameCourseAndWeeks && isContinuous) {
- current.endSection = next.endSection;
- } else if (isSameCourseAndWeeks && isDuplicate) {
- continue;
- } else {
- step1Merged.push(current);
- current = next;
- }
- }
- step1Merged.push(current);
- step1Merged.sort((a, b) => {
- return a.name.localeCompare(b.name) ||
- a.teacher.localeCompare(b.teacher) ||
- a.position.localeCompare(b.position) ||
- (a.day || 0) - (b.day || 0) ||
- (a.startSection || 0) - (b.startSection || 0) ||
- (a.endSection || 0) - (b.endSection || 0);
- });
- const step2Merged = [];
- let cur = step1Merged[0];
- for (let i = 1; i < step1Merged.length; i++) {
- const nxt = step1Merged[i];
- const isSameCourseAndSection =
- cur.name === nxt.name &&
- cur.teacher === nxt.teacher &&
- cur.position === nxt.position &&
- cur.day === nxt.day &&
- cur.startSection === nxt.startSection &&
- cur.endSection === nxt.endSection;
- if (isSameCourseAndSection) {
- cur.weeks = Array.from(new Set([...cur.weeks, ...nxt.weeks])).sort((a, b) => a - b);
- } else {
- step2Merged.push(cur);
- cur = nxt;
- }
- }
- step2Merged.push(cur);
- return step2Merged;
- }
- /**
- * 解析周次字符串,例如 "1-16周"、"1-8周,10-16周"、"1-16周(单)"、"1-16周(双)" 等。
- * @param {string} weekStr - 周次描述字符串
- * @returns {number[]} 周次数字数组(升序)
- */
- function parseWeeks(weekStr) {
- if (typeof weekStr !== 'string') {
- weekStr = String(weekStr || '');
- }
- if (!weekStr) return [];
- let cleanStr = weekStr.replace(/周/g, '').replace(/\s+/g, '');
- const weeks = new Set();
- const parts = cleanStr.split(',');
- for (let rawPart of parts) {
- if (!rawPart) continue;
- // ========== 改进点 1:先判断单双标记,再提取数字 ==========
- const hasDan = /单/.test(rawPart);
- const hasShuang = /双/.test(rawPart);
- // 仅含“单”不含“双” → 单周(奇数)
- let oddOnly = hasDan && !hasShuang;
- // 仅含“双”不含“单” → 双周(偶数)
- let evenOnly = hasShuang && !hasDan;
- // 若同时包含“单”和“双”,视为不限制奇偶(每周都上)
- // 无需额外处理,因为 oddOnly 和 evenOnly 已经为 false
- // 提取片段中的数字和连字符(忽略括号、单双等非数字字符)
- const numericPart = rawPart.replace(/[^\d\-]/g, '');
- if (!numericPart) {
- console.warn(`JS: 解析周次时忽略非法片段: ${rawPart}`);
- continue;
- }
- if (!/^[\d\-]+$/.test(numericPart)) {
- console.warn(`JS: 解析周次时忽略非法片段: ${rawPart}`);
- continue;
- }
- // 解析范围或单个数字
- if (numericPart.includes('-')) {
- const [startStr, endStr] = numericPart.split('-');
- const start = Number(startStr);
- const end = Number(endStr);
- if (!isNaN(start) && !isNaN(end) && start <= end) {
- for (let w = start; w <= end; w++) {
- if (oddOnly && w % 2 !== 1) continue; // 单周只保留奇数
- if (evenOnly && w % 2 !== 0) continue; // 双周只保留偶数
- weeks.add(w);
- }
- }
- } else {
- const w = Number(numericPart);
- if (!isNaN(w)) {
- if (oddOnly && w % 2 !== 1) continue;
- if (evenOnly && w % 2 !== 0) continue;
- weeks.add(w);
- }
- }
- }
- // 备用解析:如果正常解析为空,尝试用正则直接提取所有数字和范围
- if (weeks.size === 0) {
- // ========== 改进点 2:备用解析使用一致的奇偶判断逻辑 ==========
- const hasDanOverall = /单/.test(weekStr);
- const hasShuangOverall = /双/.test(weekStr);
- let oddOnlyOverall = hasDanOverall && !hasShuangOverall;
- let evenOnlyOverall = hasShuangOverall && !hasDanOverall;
- const rangePattern = /(\d+)\s*-\s*(\d+)/g;
- let match;
- let matched = false;
- while ((match = rangePattern.exec(weekStr)) !== null) {
- const start = Number(match[1]);
- const end = Number(match[2]);
- if (start <= end) {
- for (let w = start; w <= end; w++) {
- if (oddOnlyOverall && w % 2 === 0) continue; // 单周跳过偶数
- if (evenOnlyOverall && w % 2 !== 0) continue; // 双周跳过奇数
- weeks.add(w);
- }
- matched = true;
- }
- }
- if (!matched) {
- const singlePattern = /(\d+)周/g;
- while ((match = singlePattern.exec(weekStr)) !== null) {
- const w = Number(match[1]);
- if (!isNaN(w)) {
- if (oddOnlyOverall && w % 2 === 0) continue;
- if (evenOnlyOverall && w % 2 !== 0) continue;
- weeks.add(w);
- }
- }
- }
- }
- return Array.from(weeks).sort((a, b) => a - b);
- }
- /**
- * 解析 API 返回的 JSON 数据,提取课程信息。
- * 保持与旧版完全一致的课程解析行为。
- * @param {Object} jsonData - 教务系统返回的 JSON 对象
- * @returns {Array} 解析后的课程数组
- */
- function parseJsonData(jsonData) {
- console.log("JS: parseJsonData 正在解析 JSON 数据...");
- if (!jsonData || !Array.isArray(jsonData.kbList)) {
- console.warn("JS: JSON 数据结构错误或缺少 kbList 字段。");
- return [];
- }
- const rawCourseList = jsonData.kbList;
- const finalCourseList = [];
- for (const rawCourse of rawCourseList) {
- // 旧版严格非空校验,包含 cdmc
- if (!rawCourse.kcmc || !rawCourse.xm || !rawCourse.cdmc ||
- !rawCourse.xqj || !rawCourse.jcs || !rawCourse.zcd) {
- continue;
- }
- const weeksArray = parseWeeks(rawCourse.zcd);
- if (weeksArray.length === 0) {
- continue;
- }
- // 旧版节次解析:整体去除“节”字,按 '-' 分割取首尾
- const jcs = String(rawCourse.jcs).replace(/节/g, '');
- const sectionParts = jcs.split('-');
- const startSection = Number(sectionParts[0]);
- const endSection = Number(sectionParts[sectionParts.length - 1]);
- const day = Number(rawCourse.xqj);
- if (isNaN(day) || isNaN(startSection) || isNaN(endSection) ||
- day < 1 || day > 7 || startSection > endSection) {
- continue;
- }
- finalCourseList.push({
- name: rawCourse.kcmc.trim(),
- teacher: rawCourse.xm.trim(),
- position: rawCourse.cdmc.trim(),
- day: day,
- startSection: startSection,
- endSection: endSection,
- weeks: weeksArray
- });
- }
- // 旧版排序规则
- finalCourseList.sort((a, b) =>
- a.day - b.day ||
- a.startSection - b.startSection ||
- a.name.localeCompare(b.name)
- );
- console.log(`JS: JSON 数据解析完成,共找到 ${finalCourseList.length} 门课程。`);
- const merged = mergeAndDistinctCourses(finalCourseList);
- window.shiguangBridge.showToast(`去重前:${finalCourseList.length} 门,去重后:${merged.length} 门`);
- return merged;
- }
- /**
- * 根据课程周次数组推断学期总周数。
- * @param {Array} courses - 课程数组
- * @returns {number} 推断出的最大周次,至少为1
- */
- function inferTotalWeeks(courses) {
- let maxWeek = 0;
- for (const course of courses) {
- const weekNums = course.weeks;
- if (weekNums.length > 0) {
- maxWeek = Math.max(maxWeek, ...weekNums);
- }
- }
- // 返回至少1周,若无法推断则使用默认20
- return Math.max(1, maxWeek || 20);
- }
- /**
- * 从周次数据数组中解析第 1 周的开学日期。
- * @param {Array} weekList - 教务系统返回的周次数据数组
- * @returns {string|null} 开学日期字符串(YYYY-MM-DD),解析失败返回 null
- */
- function parseSchoolStartDate(weekList) {
- if (!Array.isArray(weekList) || weekList.length === 0) {
- return null;
- }
- // 找到第 1 周
- let firstWeek = weekList.find(item => String(item.zs) === '1');
- if (!firstWeek) {
- firstWeek = weekList.find(item => Number(item.zs) === 1);
- }
- // 如果还是没有,就按周次排序,取最小周次
- if (!firstWeek) {
- const sorted = [...weekList].sort((a, b) => Number(a.zs) - Number(b.zs));
- firstWeek = sorted[0];
- }
- if (!firstWeek) {
- return null;
- }
- // 优先从 rq 字段取开始日期
- const rq = String(firstWeek.rq || '');
- const rqMatch = rq.match(/\d{4}-\d{1,2}-\d{1,2}/);
- if (rqMatch) {
- return rqMatch[0];
- }
- // 兼容从 zcrq 字段取开始日期
- const zcrq = String(firstWeek.zcrq || '');
- const zcrqMatch = zcrq.match(/\d{4}-\d{1,2}-\d{1,2}/);
- if (zcrqMatch) {
- return zcrqMatch[0];
- }
- return null;
- }
- /**
- * 从教务系统周次数据中提取最大周次。
- * @param {Array} weekList - 教务系统周次数据数组
- * @returns {number|null} 最大周次,解析失败返回 null
- */
- function getMaxWeekFromSchoolWeekList(weekList) {
- if (!Array.isArray(weekList) || weekList.length === 0) {
- return null;
- }
- let max = 0;
- for (const item of weekList) {
- const week = Number(item.zs);
- if (!isNaN(week) && week > max) {
- max = week;
- }
- }
- return max > 0 ? max : null;
- }
- function getDefaultSemesterDate(academicYear, semesterIndex) {
- const year = Number(academicYear);
- if (isNaN(year)) return "2026-02-01";
- if (semesterIndex === 0) {
- return `${year}-09-01`;
- } else {
- return `${year + 1}-02-01`;
- }
- }
- // ===================== 全局常量 =====================
- const BASE_URL = 'https://jwglxt.gpnu.edu.cn';
- // ===================== 全局验证函数 =====================
- /**
- * 判断一个字符串是否为有效的日期(YYYY-MM-DD),并检查日期是否真实存在。
- * @param {string} dateStr - 日期字符串
- * @returns {boolean} 是否有效
- */
- function isValidDateString(dateStr) {
- if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return false;
- const [year, month, day] = dateStr.split('-').map(Number);
- if (year < 2000 || year > 2100) return false;
- if (month < 1 || month > 12) return false;
- const daysInMonth = new Date(year, month, 0).getDate();
- return day >= 1 && day <= daysInMonth;
- }
- /**
- * 校验学年输入:必须为四位数字,且在合理范围内(2000-2100)。
- * @param {string|null|undefined} input - 用户输入
- * @returns {string|false} 校验失败返回提示字符串,通过返回 false
- */
- function validateYearInput(input) {
- console.log("JS: validateYearInput被调用,输入: " + input);
- if (input === null || input === undefined) {
- return "请输入四位数字的学年喵~";
- }
- if (/^[0-9]{4}$/.test(input)) {
- const year = Number(input);
- if (year >= 2000 && year <= 2100) {
- return false;
- } else {
- return "学年需在 2000 到 2100 之间喵~";
- }
- } else {
- return "请输入四位数字的学年喵~";
- }
- }
- /**
- * 校验日期输入:允许空值,格式必须为 YYYY-MM-DD,且日期有效。
- * @param {string|null|undefined} input - 用户输入
- * @returns {string|false} 校验失败返回提示字符串,通过返回 false
- */
- function validateDateInput(input) {
- if (input === null || input === undefined || input.trim() === '') {
- return false;
- }
- if (isValidDateString(input.trim())) {
- return false;
- }
- return "日期格式应为 YYYY-MM-DD,且为有效日期喵~";
- }
- /**
- * 校验学期总周数输入:必须为 1~30 的整数。
- * @param {string|null|undefined} input - 用户输入
- * @returns {string|false} 校验失败返回提示字符串,通过返回 false
- */
- function validateWeeksInput(input) {
- if (input === null || input === undefined || input.trim() === '') {
- return "请输入学期总周数(1-30)喵~";
- }
- const num = Number(input.trim());
- if (Number.isInteger(num) && num >= 1 && num <= 30) {
- return false;
- }
- return "周数必须是 1 到 30 之间的整数喵~";
- }
- // ===================== 与原生交互的异步封装 =====================
- /**
- * 询问用户是否退出当前导入流程。
- * @returns {Promise<boolean>} true=退出,false=留在页面
- */
- async function promptUserToExit() {
- console.log("JS: 询问用户是否退出。");
- const options = ["退出", "留在页面"];
- const rawIndex = await window.shiguangBridgePromise.showSingleSelection(
- "导入流程已结束",
- JSON.stringify(options),
- 0 // 默认选中“退出”
- );
- // 用户取消选择(返回 null/undefined/'')时,默认不退出
- if (rawIndex === null || rawIndex === undefined || rawIndex === '') {
- console.log("JS: 用户取消退出选择,默认留在页面。");
- return false;
- }
- const index = Number(rawIndex);
- if (isNaN(index) || index < 0 || index >= options.length) {
- console.warn("JS: 退出选择索引无效,默认留在页面。");
- return false;
- }
- return index === 0;
- }
- async function promptUserToStart() {
- console.log("JS: 流程开始:显示公告。");
- return await window.shiguangBridgePromise.showAlert(
- "教务系统课表导入喵~",
- "Ciallo~ 导入前请确保您已成功登录教务系统哦喵~",
- "好的"
- );
- }
- /**
- * 显示课程导入成功的弹窗公告。
- * @param {number} length - 导入的课程数量
- */
- async function promptUserToSUCCESS(length) {
- console.log("JS: SUCCESS:显示公告。");
- return await window.shiguangBridgePromise.showAlert(
- "SUCCESS",
- `成功了喵~ 共导入 ${length} 门课程!`,
- "好的"
- );
- }
- const SCHOOL_START_MONTH = 8; // 默认
- async function getAcademicYear() {
- const now = new Date();
- let academicYear = now.getFullYear();
- if (now.getMonth() + 1 < SCHOOL_START_MONTH) {
- academicYear -= 1;
- }
- const defaultYear = academicYear.toString();
- return await window.shiguangBridgePromise.showPrompt(
- "选择学年喵~(Auto value)",
- "请输入要导入课程的起始学年喵~:",
- defaultYear,
- "validateYearInput"
- );
- }
- async function selectSemester() {
- const semesters = ["第一学期", "第二学期"];
- console.log("JS: 提示用户选择学期。");
- const rawIndex = await window.shiguangBridgePromise.showSingleSelection(
- "选择学期喵~",
- JSON.stringify(semesters),
- 0
- );
- if (rawIndex === null || rawIndex === undefined || rawIndex === '') {
- console.log("JS: 用户取消选择学期。");
- return null;
- }
- const index = Number(rawIndex);
- if (isNaN(index) || index < 0 || index >= semesters.length) {
- console.warn("JS: 学期索引无效,返回 null。");
- return null;
- }
- return index;
- }
- async function selectArea() {
- const areas = ["东/西/北校区", "白云校区", "河源校区"];
- console.log("JS: 提示用户选择校区。");
- const rawIndex = await window.shiguangBridgePromise.showSingleSelection(
- "选择校区喵~",
- JSON.stringify(areas),
- 0
- );
- if (rawIndex === null || rawIndex === undefined || rawIndex === '') {
- console.log("JS: 用户取消选择校区。");
- return null;
- }
- const index = Number(rawIndex);
- if (isNaN(index) || index < 0 || index >= areas.length) {
- console.warn("JS: 校区索引无效,返回 null。");
- return null;
- }
- return index;
- }
- async function selectTotalWeeksSource(courseMaxWeeks, schoolMaxWeeks) {
- const options = [
- `课程最大周数(${courseMaxWeeks}周)`,
- schoolMaxWeeks ? `教务系统周数(${schoolMaxWeeks}周)` : "教务系统周数(无数据)",
- "手动输入"
- ];
- console.log("JS: 提示用户选择周数来源。");
- const rawIndex = await window.shiguangBridgePromise.showSingleSelection(
- "选择学期总周数来源喵~(已自动解析)",
- JSON.stringify(options),
- 0
- );
- if (rawIndex === null || rawIndex === undefined || rawIndex === '') {
- console.log("JS: 用户取消选择周数来源。");
- return null;
- }
- const index = Number(rawIndex);
- if (isNaN(index) || index < 0 || index > 2) {
- console.warn("JS: 周数来源选择无效,默认使用课程最大周数。");
- return 0;
- }
- return index;
- }
- /**
- * 获取学期开始日期。
- * 优先使用传入的周次数据解析开学日期;若未传入或数据无效,则向教务系统请求。
- * 解析成功后弹出对话框让用户确认或修改,用户可留空跳过(返回 null)。
- * @param {string} academicYear - 学年(四位数字)
- * @param {number} semesterIndex - 学期索引(0 第一学期,1 第二学期)
- * @param {Array|null} [weekList=null] - 可选的周次数据数组,若提供且非空则直接使用
- * @returns {Promise<string|null>} 用户确认的日期字符串(YYYY-MM-DD),取消或留空返回 null
- */
- async function getSemesterStartDate(academicYear, semesterIndex, weekList = null) {
- console.log("JS: 正在获取学期周次数据以解析开学日期...");
- let defaultDate = getDefaultSemesterDate(academicYear, semesterIndex);
- // 1. 确定要使用的周次数据来源
- let data = weekList;
- if (!Array.isArray(data) || data.length === 0) {
- // 外部没有提供有效数据,尝试从教务系统获取
- try {
- data = await fetchSchoolWeekData(academicYear, getSemesterCode(semesterIndex));
- } catch (error) {
- console.warn("JS: 获取周次数据失败,将使用动态默认日期。", error);
- window.shiguangBridge.showToast("未能获取学期周次数据,开始日期可能需要手动填写。");
- data = null; // 确保后续不会使用无效数据
- }
- }
- // 2. 解析开学日期
- if (Array.isArray(data) && data.length > 0) {
- const parsedDate = parseSchoolStartDate(data);
- if (parsedDate) {
- defaultDate = parsedDate;
- console.log(`JS: 解析到的开学日期: ${defaultDate}`);
- } else {
- console.log("JS: 未能从周次数据解析出开学日期,使用动态默认值。");
- }
- } else {
- console.log("JS: 无可用周次数据,使用动态默认值。");
- }
- // 3. 弹出对话框让用户确认或修改日期(可留空跳过)
- console.log("JS: 提示用户输入学期开始日期(可留空跳过)。");
- const input = await window.shiguangBridgePromise.showPrompt(
- "学期开始日期喵~",
- "请输入学期第一天的日期喵~(YYYY-MM-DD)(已尝试自动解析)",
- defaultDate,
- "validateDateInput"
- );
- if (input === null || input === undefined) {
- console.log("JS: 用户取消了开始日期输入,继续流程。");
- return null;
- }
- if (input.trim() === '') {
- return null;
- }
- return input.trim();
- }
- async function getSemesterTotalWeeks(defaultWeeks) {
- console.log(`JS: 提示用户输入学期总周数,默认值:${defaultWeeks}`);
- const input = await window.shiguangBridgePromise.showPrompt(
- "设置学期总周数喵~",
- "请输入本学期的总周数喵~(1-30)",
- String(defaultWeeks),
- "validateWeeksInput"
- );
- if (input === null || input === undefined) {
- console.log("JS: 用户取消了总周数输入,流程继续使用默认值。");
- return defaultWeeks;
- }
- const weeks = Number(input.trim());
- if (!Number.isInteger(weeks) || weeks < 1 || weeks > 30) {
- console.warn("JS: 周数输入非法,使用默认值。");
- return defaultWeeks;
- }
- return weeks;
- }
- async function promptImportTimeSlots() {
- console.log("JS: 询问用户是否导入预设时间段。");
- return await window.shiguangBridgePromise.showAlert(
- "导入预设时间段喵~",
- "是否导入该校区对应的预设上课时间段喵~?",
- "导入"
- );
- }
- // ===================== 网络请求 =====================
- async function fetchCourseData(academicYear, semesterIndex) {
- const semesterCode = getSemesterCode(semesterIndex);
- const requestBody = new URLSearchParams({
- gnmkdm: 'N2151',
- xnm: academicYear,
- xqm: semesterCode,
- kzlx: 'ck',
- xsdm: '',
- kclbdm: '',
- kclxdm: ''
- }).toString();
- const targetUrls = [
- `${BASE_URL}/jwglxt/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151`,
- ];
- const errors = [];
- for (const url of targetUrls) {
- try {
- const response = await fetch(url, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
- "X-Requested-With": "XMLHttpRequest"
- },
- body: requestBody,
- credentials: "include"
- });
- if (!response.ok) {
- const msg = `HTTP ${response.status} ${response.statusText}`;
- errors.push(`${url} -> ${msg}`);
- console.warn(`课表数据请求失败(${msg}):${url}`);
- continue;
- }
- const jsonText = await response.text();
- if (jsonText.includes('<html')) {
- errors.push(`${url} -> 返回登录页面,可能登录已过期`);
- console.warn(`课表数据返回登录页面,可能未登录或会话过期:${url}`);
- continue;
- }
- let data;
- try {
- data = JSON.parse(jsonText);
- } catch (parseError) {
- errors.push(`${url} -> JSON 解析失败: ${parseError.message}`);
- console.warn(`课表数据 JSON 解析失败:${url}`, parseError);
- continue;
- }
- if (!data || !Array.isArray(data.kbList)) {
- errors.push(`${url} -> 返回数据缺少 kbList 数组`);
- console.warn(`课表数据缺少 kbList 字段:${url}`, data);
- continue;
- }
- return data;
- } catch (networkError) {
- errors.push(`${url} -> 网络异常: ${networkError.message}`);
- console.warn(`课表数据网络请求异常:${url}`, networkError);
- }
- }
- const errorMessage = errors.length > 0
- ? `课表数据获取失败,尝试了 ${targetUrls.length} 个地址:\n${errors.join('\n')}`
- : '课表数据获取失败,未配置任何请求地址';
- console.error(errorMessage);
- throw new Error(errorMessage);
- }
- async function fetchSchoolWeekData(xnm, xqm) {
- const targetUrls = [
- `${BASE_URL}/jwglxt/kbcx/xskbcxZccx_cxZcByXnxq.html?gnmkdm=N2154`,
- ];
- const requestBody = new URLSearchParams({
- xnm: xnm,
- xqm: xqm
- }).toString();
- const errors = [];
- for (const url of targetUrls) {
- try {
- const response = await fetch(url, {
- headers: {
- 'accept': 'application/json, text/javascript, */*; q=0.01',
- 'accept-language': 'zh-CN,zh;q=0.9',
- 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
- 'x-requested-with': 'XMLHttpRequest'
- },
- body: requestBody,
- method: 'POST',
- mode: 'cors',
- credentials: 'include'
- });
- if (!response.ok) {
- const msg = `HTTP ${response.status} ${response.statusText}`;
- errors.push(`${url} -> ${msg}`);
- console.warn(`周次数据请求失败(${msg}):${url}`);
- continue;
- }
- const text = await response.text();
- if (text.includes('<html')) {
- errors.push(`${url} -> 返回登录页面,可能登录已过期`);
- console.warn(`周次数据返回登录页面,可能未登录或会话过期:${url}`);
- continue;
- }
- let data;
- try {
- data = JSON.parse(text);
- } catch (parseError) {
- errors.push(`${url} -> JSON 解析失败: ${parseError.message}`);
- console.warn(`周次数据 JSON 解析失败:${url}`, parseError);
- continue;
- }
- if (!Array.isArray(data)) {
- const msg = '返回数据不是数组';
- errors.push(`${url} -> ${msg}`);
- console.warn(`周次数据格式错误(期望数组):${url}`, data);
- continue;
- }
- return data;
- } catch (networkError) {
- errors.push(`${url} -> 网络异常: ${networkError.message}`);
- console.warn(`周次数据网络请求异常:${url}`, networkError);
- }
- }
- const errorMessage = errors.length > 0
- ? `周次数据获取失败,尝试了 ${targetUrls.length} 个地址:\n${errors.join('\n')}`
- : '周次数据获取失败,未配置任何请求地址';
- console.error(errorMessage);
- throw new Error(errorMessage);
- }
- // ===================== 数据解析与配置构建 =====================
- function buildCourseDataFromRaw(rawData, startDate) {
- console.log("JS: buildCourseDataFromRaw 开始解析原始数据...");
- if (!rawData || !Array.isArray(rawData.kbList)) {
- throw new Error('课表数据格式错误或缺少 kbList 字段');
- }
- const courses = parseJsonData(rawData);
- if (courses.length === 0) {
- throw new Error('未解析到有效课程,请检查课表数据');
- }
- const inferredWeeks = inferTotalWeeks(courses);
- const config = {
- semesterStartDate: startDate,
- semesterTotalWeeks: inferredWeeks
- };
- console.log(`JS: 解析成功,课程数:${courses.length},推断总周数:${inferredWeeks}`);
- return { courses, config };
- }
- // ===================== 数据保存 =====================
- async function saveCourses(parsedCourses) {
- window.shiguangBridge.showToast(`正在保存 ${parsedCourses.length} 门课程...`);
- console.log(`JS: 尝试保存 ${parsedCourses.length} 门课程...`);
- try {
- await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
- console.log("JS: 课程保存成功!");
- return true;
- } catch (error) {
- window.shiguangBridge.showToast(`课程保存失败: ${error.message}`);
- console.error('JS: Save Courses Error:', error);
- return false;
- }
- }
- async function importPresetTimeSlots(timeSlots) {
- console.log(`JS: 准备导入 ${timeSlots.length} 个预设时间段。`);
- if (timeSlots.length > 0) {
- window.shiguangBridge.showToast(`正在导入 ${timeSlots.length} 个预设时间段...`);
- try {
- await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- window.shiguangBridge.showToast("预设时间段导入成功!");
- } catch (error) {
- window.shiguangBridge.showToast("导入时间段失败: " + error.message);
- console.error('JS: Save Time Slots Error:', error);
- }
- } else {
- window.shiguangBridge.showToast("警告:时间段为空,未导入时间段信息。");
- console.warn("JS: 警告:传入时间段为空,未导入时间段信息。");
- }
- }
- // ===================== 三个校区的时间表 =====================
- const TimeSlots_one = [
- { number: 1, startTime: "08:20", endTime: "09:00" },
- { number: 2, startTime: "09:10", endTime: "09:50" },
- { number: 3, startTime: "10:00", endTime: "10:40" },
- { number: 4, startTime: "10:50", endTime: "11:30" },
- { number: 5, startTime: "13:30", endTime: "14:10" },
- { number: 6, startTime: "14:20", endTime: "15:00" },
- { number: 7, startTime: "15:10", endTime: "15:50" },
- { number: 8, startTime: "16:00", endTime: "16:40" },
- { number: 9, startTime: "18:40", endTime: "19:20" },
- { number: 10, startTime: "19:30", endTime: "20:10" },
- { number: 11, startTime: "20:20", endTime: "21:00" }
- ];
- const TimeSlots_two = [
- { number: 1, startTime: "08:30", endTime: "09:10" },
- { number: 2, startTime: "09:15", endTime: "09:55" },
- { number: 3, startTime: "10:05", endTime: "10:45" },
- { number: 4, startTime: "10:50", endTime: "11:30" },
- { number: 5, startTime: "13:30", endTime: "14:10" },
- { number: 6, startTime: "14:15", endTime: "14:55" },
- { number: 7, startTime: "15:05", endTime: "15:45" },
- { number: 8, startTime: "15:50", endTime: "16:30" },
- { number: 9, startTime: "18:40", endTime: "19:20" },
- { number: 10, startTime: "19:25", endTime: "20:05" },
- { number: 11, startTime: "20:10", endTime: "20:50" }
- ];
- const TimeSlots_three = [
- { number: 1, startTime: "08:20", endTime: "09:00" },
- { number: 2, startTime: "09:10", endTime: "09:50" },
- { number: 3, startTime: "10:10", endTime: "10:50" },
- { number: 4, startTime: "11:00", endTime: "11:40" },
- { number: 5, startTime: "13:50", endTime: "14:30" },
- { number: 6, startTime: "14:40", endTime: "15:20" },
- { number: 7, startTime: "15:40", endTime: "16:20" },
- { number: 8, startTime: "16:30", endTime: "17:10" },
- { number: 9, startTime: "18:40", endTime: "19:20" },
- { number: 10, startTime: "19:30", endTime: "20:10" },
- { number: 11, startTime: "20:20", endTime: "21:00" }
- ];
- // ===================== 索引 =====================
- function getSemesterCode(semesterIndex) {
- return semesterIndex === 0 ? "3" : "12";
- }
- function getTimeSlotsByAreaIndex(areaIndex) {
- if (areaIndex === 0) return TimeSlots_one;
- if (areaIndex === 1) return TimeSlots_two;
- if (areaIndex === 2) return TimeSlots_three;
- return TimeSlots_one;
- }
- // ===================== 主流程 =====================
- async function runImportFlow() {
- let shouldExit = false; // 最终是否退出(由用户选择决定)
- let flowEnded = false; // 标记流程是否已经走到需要询问退出的节点
- try {
- // ================= 流程开始 =================
- const alertConfirmed = await promptUserToStart();
- if (!alertConfirmed) {
- window.shiguangBridge.showToast("导入已取消。");
- console.log("JS: 用户取消了导入流程。");
- flowEnded = true;
- return; // 直接返回,但 finally 中会询问是否退出
- }
- // 获取学年
- const academicYear = await getAcademicYear();
- if (academicYear === null || academicYear === undefined) {
- window.shiguangBridge.showToast("导入已取消。");
- console.log("JS: 获取学年失败/取消,流程终止。");
- flowEnded = true;
- return;
- }
- console.log(`JS: 已选择学年: ${academicYear}`);
- // 选择学期
- const semesterIndex = await selectSemester();
- if (semesterIndex === null) {
- window.shiguangBridge.showToast("导入已取消。");
- console.log("JS: 选择学期失败/取消,流程终止。");
- flowEnded = true;
- return;
- }
- console.log(`JS: 已选择学期索引: ${semesterIndex}`);
- // 选择校区
- const areaIndex = await selectArea();
- if (areaIndex === null) {
- window.shiguangBridge.showToast("导入已取消。");
- console.log("JS: 选择校区失败/取消,流程终止。");
- flowEnded = true;
- return;
- }
- console.log(`JS: 已选择校区索引: ${areaIndex}`);
- // 获取周次数据(可选)
- let schoolWeekList = null;
- try {
- schoolWeekList = await fetchSchoolWeekData(academicYear, getSemesterCode(semesterIndex));
- } catch (error) {
- console.warn("JS: 获取周次数据失败,稍后周数来源可能缺少教务系统选项。", error);
- window.shiguangBridge.showToast("获取周次数据失败,将使用课程周数或手动输入。");
- schoolWeekList = [];
- }
- // 获取学期开始日期
- const startDate = await getSemesterStartDate(academicYear, semesterIndex, schoolWeekList);
- console.log(`JS: 学期开始日期输入结果: ${startDate}`);
- // 获取课表原始数据
- const rawData = await fetchCourseData(academicYear, semesterIndex);
- // 解析课程
- let courses, config;
- try {
- const parsedResult = buildCourseDataFromRaw(rawData, startDate);
- courses = parsedResult.courses;
- config = parsedResult.config;
- } catch (error) {
- window.shiguangBridge.showToast(error.message);
- console.error('JS: 课程解析失败:', error);
- flowEnded = true;
- return;
- }
- // 周数来源选择
- const courseMaxWeeks = config.semesterTotalWeeks;
- const schoolMaxWeeks = getMaxWeekFromSchoolWeekList(schoolWeekList);
- const weekSource = await selectTotalWeeksSource(courseMaxWeeks, schoolMaxWeeks);
- if (weekSource === null) {
- window.shiguangBridge.showToast("未选择周数来源,导入已取消。");
- console.log("JS: 用户取消周数来源选择,流程终止。");
- flowEnded = true;
- return;
- }
- let finalWeeks;
- if (weekSource === 0) {
- finalWeeks = courseMaxWeeks;
- } else if (weekSource === 1) {
- if (schoolMaxWeeks) {
- finalWeeks = schoolMaxWeeks;
- } else {
- window.shiguangBridge.showToast("教务系统周数不可用,已使用课程最大周数。");
- finalWeeks = courseMaxWeeks;
- }
- } else {
- finalWeeks = await getSemesterTotalWeeks(courseMaxWeeks);
- }
- config.semesterTotalWeeks = finalWeeks;
- // 保存课程
- const saveResult = await saveCourses(courses);
- if (!saveResult) {
- console.log("JS: 课程保存失败,流程终止。");
- flowEnded = true;
- return;
- }
- // 保存配置
- try {
- await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
- window.shiguangBridge.showToast(`课表配置更新成功!总周数:${config.semesterTotalWeeks}周。`);
- } catch (error) {
- window.shiguangBridge.showToast(`课表配置保存失败: ${error.message}`);
- console.error('JS: Save Config Error:', error);
- }
- // 导入预设时间段
- const timeSlots = getTimeSlotsByAreaIndex(areaIndex);
- const shouldImportTimeSlots = await promptImportTimeSlots();
- if (shouldImportTimeSlots) {
- await importPresetTimeSlots(timeSlots);
- } else {
- console.log("JS: 用户取消导入预设时间段,跳过。");
- window.shiguangBridge.showToast("已跳过导入时间段。");
- }
- await promptUserToSUCCESS(courses.length);
- console.log("JS: 整个导入流程执行完毕并成功。");
- flowEnded = true; // 成功结束
- } catch (e) {
- console.error('JS: 导入流程异常:', e);
- try {
- window.shiguangBridge.showToast('导入失败:' + (e && e.message ? e.message : e));
- } catch (_) {}
- flowEnded = true; // 异常结束
- } finally {
- // 无论成功、失败、取消,只要流程已经结束,就询问是否退出
- if (flowEnded) {
- shouldExit = await promptUserToExit();
- if (shouldExit) {
- console.log("JS: 用户选择退出,通知原生任务结束。");
- try {
- window.shiguangBridge.notifyTaskCompletion();
- } catch (error) {
- console.error("JS: 调用 notifyTaskCompletion 失败:", error);
- }
- } else {
- console.log("JS: 用户选择留在页面,不通知原生。");
- // 可在此处添加刷新页面或保持现状的逻辑
- window.location.reload();
- }
- }
- }
- }
- runImportFlow();
|