|
|
@@ -1,4 +1,8 @@
|
|
|
-// 文件: HUAT1_fixed_shift.js - 修复节次偏移 + 季节时间选择 (夏季/秋季)
|
|
|
+// 文件: HUAT.js - 湖北汽车工业学院课表导入(基于教务系统 API)
|
|
|
+// 数据源:
|
|
|
+// /api/semester/list → 可选学期列表(用于学期选择 + 学期基本信息)
|
|
|
+// /api/teachTask/list → 教学任务列表(按 yearTermId 过滤)
|
|
|
+
|
|
|
// ==================== 验证函数 ====================
|
|
|
function validateDate(dateStr) {
|
|
|
if (!dateStr || dateStr.trim().length === 0) {
|
|
|
@@ -18,430 +22,225 @@ function validateDate(dateStr) {
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
-function validateName(name) {
|
|
|
- if (name === null || name.trim().length === 0) {
|
|
|
- return "输入不能为空!";
|
|
|
- }
|
|
|
- if (name.length < 2) {
|
|
|
- return "至少需要2个字符!";
|
|
|
+
|
|
|
+// ==================== API 数据获取 ====================
|
|
|
+// 用当前页面协议构造 API 基址,避免 HTTP/HTTPS 跨域
|
|
|
+// (http:// 和 https:// 在 CORS 看来是不同 origin,会触发预检并失败)
|
|
|
+const API_BASE = window.location.protocol + '//neweas.huat.edu.cn';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取认证 token(X-Token 头的值)
|
|
|
+ * 优先级:localStorage / sessionStorage 常见 key → Admin-Token cookie → window 全局
|
|
|
+ */
|
|
|
+function getAuthToken() {
|
|
|
+ const storageKeys = ['Admin-Token', 'adminToken', 'token', 'X-Token', 'x-token', 'admin_token'];
|
|
|
+ for (const key of storageKeys) {
|
|
|
+ const v = localStorage.getItem(key) || sessionStorage.getItem(key);
|
|
|
+ if (v) return v;
|
|
|
}
|
|
|
- return false;
|
|
|
+ // 从 cookie 读 Admin-Token
|
|
|
+ const cookieMatch = document.cookie.match(/Admin-Token=([^;]+)/);
|
|
|
+ if (cookieMatch) return cookieMatch[1];
|
|
|
+ // window 全局
|
|
|
+ if (window.token) return window.token;
|
|
|
+ if (window.userToken) return window.userToken;
|
|
|
+ return null;
|
|
|
}
|
|
|
-// ==================== 课表数据提取函数 ====================
|
|
|
+
|
|
|
/**
|
|
|
- * 从页面提取课表数据 - 修复3-4和7-8节偏移
|
|
|
+ * 解析 JWT payload(仅解码,不验签)
|
|
|
*/
|
|
|
-function extractCourseData() {
|
|
|
- console.log("开始提取湖北汽车工业学院课表数据...");
|
|
|
-
|
|
|
- const courses = [];
|
|
|
-
|
|
|
- // 获取所有课表行 - 使用更精确的选择器
|
|
|
- const rows = document.querySelectorAll('.el-table__body-wrapper tbody tr');
|
|
|
- console.log(`找到 ${rows.length} 行课表数据`);
|
|
|
-
|
|
|
- // 获取时间标签,用于验证行对应的节次
|
|
|
- const timeLabels = document.querySelectorAll('.el-table__header-wrapper th .cell, .el-table__header-wrapper th span');
|
|
|
- console.log("时间标签:", Array.from(timeLabels).map(el => el.textContent));
|
|
|
-
|
|
|
- rows.forEach((row, rowIndex) => {
|
|
|
- // 获取该行的所有单元格
|
|
|
- const cells = row.querySelectorAll('td');
|
|
|
-
|
|
|
- // 从第1个单元格开始是周一至周日(跳过第0个时间单元格)
|
|
|
- for (let dayIndex = 1; dayIndex < cells.length; dayIndex++) {
|
|
|
- const cell = cells[dayIndex];
|
|
|
-
|
|
|
- // 星期映射修正:第1列=周一(0), 第2列=周二(1), ... 第7列=周日(6)
|
|
|
- let day = dayIndex - 1;
|
|
|
-
|
|
|
- // 修复3-4节和7-8节的偏移
|
|
|
- // 3-4节在行索引2-3,7-8节在行索引6-7
|
|
|
- if ((rowIndex >= 2 && rowIndex <= 3) || (rowIndex >= 6 && rowIndex <= 7)) {
|
|
|
- // 将周一向后推一天
|
|
|
- if (day === 0) day = 1; // 周一 -> 周二
|
|
|
- else if (day === 1) day = 2; // 周二 -> 周三
|
|
|
- else if (day === 2) day = 3; // 周三 -> 周四
|
|
|
- else if (day === 3) day = 4; // 周四 -> 周五
|
|
|
- else if (day === 4) day = 5; // 周五 -> 周六
|
|
|
- else if (day === 5) day = 6; // 周六 -> 周日
|
|
|
- // 周日(6)保持不变
|
|
|
- }
|
|
|
-
|
|
|
- // 查找单元格内的所有课程块 - 使用更通用的选择器
|
|
|
- const courseBlocks = cell.querySelectorAll('[class*="theory"], .theory, [class*="course"], div[style*="background"]');
|
|
|
-
|
|
|
- if (courseBlocks.length > 0) {
|
|
|
- courseBlocks.forEach(block => {
|
|
|
- try {
|
|
|
- const course = parseCourseBlock(block, day, rowIndex);
|
|
|
- if (course) {
|
|
|
- courses.push(course);
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error("解析课程块失败:", e);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 如果没找到课程,尝试另一种选择器
|
|
|
- if (courses.length === 0) {
|
|
|
- console.log("尝试使用备用选择器...");
|
|
|
- const allCourseElements = document.querySelectorAll('[class*="theory"], .theory, [class*="course"]');
|
|
|
- console.log(`找到 ${allCourseElements.length} 个可能的课程元素`);
|
|
|
-
|
|
|
- allCourseElements.forEach(element => {
|
|
|
- // 尝试找到元素所在的单元格和行
|
|
|
- const td = element.closest('td');
|
|
|
- if (td) {
|
|
|
- const tr = td.closest('tr');
|
|
|
- if (tr) {
|
|
|
- const rowIndex = Array.from(tr.parentNode.children).indexOf(tr);
|
|
|
- const dayIndex = Array.from(td.parentNode.children).indexOf(td);
|
|
|
-
|
|
|
- if (dayIndex >= 1 && dayIndex <= 7) {
|
|
|
- // 星期映射修正:第1列=周一(0), 第2列=周二(1), ... 第7列=周日(6)
|
|
|
- let day = dayIndex - 1;
|
|
|
-
|
|
|
- // 修复3-4节和7-8节的偏移
|
|
|
- if ((rowIndex >= 2 && rowIndex <= 3) || (rowIndex >= 6 && rowIndex <= 7)) {
|
|
|
- if (day === 0) day = 1;
|
|
|
- else if (day === 1) day = 2;
|
|
|
- else if (day === 2) day = 3;
|
|
|
- else if (day === 3) day = 4;
|
|
|
- else if (day === 4) day = 5;
|
|
|
- else if (day === 5) day = 6;
|
|
|
- }
|
|
|
-
|
|
|
- const course = parseCourseBlock(element, day, rowIndex);
|
|
|
- if (course) {
|
|
|
- courses.push(course);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
+function decodeJwtPayload(jwt) {
|
|
|
+ if (!jwt) return null;
|
|
|
+ const parts = jwt.split('.');
|
|
|
+ if (parts.length !== 3) return null;
|
|
|
+ try {
|
|
|
+ const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
|
+ const json = decodeURIComponent(atob(b64).split('').map(c =>
|
|
|
+ '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
|
|
+ ).join(''));
|
|
|
+ return JSON.parse(json);
|
|
|
+ } catch (e) {
|
|
|
+ return null;
|
|
|
}
|
|
|
-
|
|
|
- // 去重
|
|
|
- const uniqueCourses = removeDuplicates(courses);
|
|
|
-
|
|
|
- // 按星期和节次排序
|
|
|
- uniqueCourses.sort((a, b) => {
|
|
|
- if (a.day !== b.day) return a.day - b.day;
|
|
|
- return a.startSection - b.startSection;
|
|
|
- });
|
|
|
-
|
|
|
- console.log(`共提取到 ${uniqueCourses.length} 门课程`);
|
|
|
- uniqueCourses.forEach(c => {
|
|
|
- console.log(`星期${c.day+1}: ${c.name} - ${c.teacher} - ${c.position} - 第${c.startSection}-${c.endSection}节`);
|
|
|
- });
|
|
|
-
|
|
|
- return uniqueCourses;
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
- * 解析课程块 - 修复节次映射
|
|
|
+ * 从 JWT 的 sub 字段提取学生 ID
|
|
|
*/
|
|
|
-function parseCourseBlock(block, day, rowIndex) {
|
|
|
- // 获取所有子元素
|
|
|
- const children = block.children;
|
|
|
- let name = '', teacher = '', position = '', weeks = [];
|
|
|
-
|
|
|
- // 方法1:通过子元素解析
|
|
|
- if (children.length >= 3) {
|
|
|
- // 课程名称通常在h3标签中
|
|
|
- const nameElement = block.querySelector('h3');
|
|
|
- if (nameElement) {
|
|
|
- name = nameElement.innerText.trim();
|
|
|
- // 只移除括号内是纯数字且不是课程名称标识的情况
|
|
|
- // 但保留(24)这种课程代码
|
|
|
- // 如果括号内是纯数字且长度大于2,可能是周次信息,否则保留
|
|
|
- name = name.replace(/[((]\d+[))]/g, function(match) {
|
|
|
- // 提取括号内的数字
|
|
|
- const num = match.replace(/[(())]/g, '');
|
|
|
- // 如果数字大于30,可能是周次信息,移除;否则保留(如24是课程代码)
|
|
|
- if (parseInt(num) > 30) {
|
|
|
- return '';
|
|
|
- }
|
|
|
- return match;
|
|
|
- }).trim();
|
|
|
- }
|
|
|
-
|
|
|
- // 教师信息通常在第一个p标签中
|
|
|
- const teacherElement = block.querySelector('p:first-child span:first-child, p:nth-child(1) span');
|
|
|
- if (teacherElement) {
|
|
|
- teacher = teacherElement.innerText.trim();
|
|
|
- // 移除课时信息(如"2H")
|
|
|
- teacher = teacher.replace(/\d+H?$/, '').trim();
|
|
|
- }
|
|
|
-
|
|
|
- // 周次和教室信息通常在第二个p标签中
|
|
|
- const weekPositionElement = block.querySelector('p:nth-child(2) span, p:last-child span');
|
|
|
- if (weekPositionElement) {
|
|
|
- const text = weekPositionElement.innerText.trim();
|
|
|
- const result = parseWeekAndPosition(text);
|
|
|
- weeks = result.weeks;
|
|
|
- position = result.position;
|
|
|
- }
|
|
|
+function getStudentId() {
|
|
|
+ const token = getAuthToken();
|
|
|
+ if (!token) return null;
|
|
|
+ const payload = decodeJwtPayload(token);
|
|
|
+ return (payload && payload.sub) || null;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 诊断信息:token 来源 + 预览 + 当前页面 URL
|
|
|
+ * 用于手机端排查认证失败问题(手机端通常没 DevTools 可看)
|
|
|
+ */
|
|
|
+function getTokenDiagnostic() {
|
|
|
+ const storageKeys = ['Admin-Token', 'adminToken', 'token', 'X-Token', 'x-token', 'admin_token'];
|
|
|
+ let source = '未找到';
|
|
|
+ let token = null;
|
|
|
+
|
|
|
+ for (const key of storageKeys) {
|
|
|
+ const v = localStorage.getItem(key);
|
|
|
+ if (v) { token = v; source = 'localStorage[' + key + ']'; break; }
|
|
|
}
|
|
|
-
|
|
|
- // 方法2:如果子元素解析失败,通过文本行解析
|
|
|
- if (!name || !teacher || !position) {
|
|
|
- const text = block.innerText;
|
|
|
- const lines = text.split('\n').filter(line => line.trim());
|
|
|
-
|
|
|
- if (lines.length >= 3) {
|
|
|
- // 第1行:课程名称
|
|
|
- if (!name) {
|
|
|
- name = lines[0].trim();
|
|
|
- // 只移除括号内是纯数字且不是课程名称标识的情况
|
|
|
- name = name.replace(/[((]\d+[))]/g, function(match) {
|
|
|
- const num = match.replace(/[(())]/g, '');
|
|
|
- if (parseInt(num) > 30) {
|
|
|
- return '';
|
|
|
- }
|
|
|
- return match;
|
|
|
- }).trim();
|
|
|
- }
|
|
|
-
|
|
|
- // 第2行:教师信息
|
|
|
- if (!teacher && lines[1]) {
|
|
|
- teacher = lines[1].trim();
|
|
|
- teacher = teacher.replace(/\d+H?$/, '').trim();
|
|
|
- }
|
|
|
-
|
|
|
- // 第3行:周次和教室
|
|
|
- if (!position && lines[2]) {
|
|
|
- const result = parseWeekAndPosition(lines[2].trim());
|
|
|
- weeks = result.weeks;
|
|
|
- position = result.position;
|
|
|
- }
|
|
|
+ if (!token) {
|
|
|
+ for (const key of storageKeys) {
|
|
|
+ const v = sessionStorage.getItem(key);
|
|
|
+ if (v) { token = v; source = 'sessionStorage[' + key + ']'; break; }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // 如果还是没有找到教室,尝试从整个块中提取数字教室
|
|
|
- if (!position || position === '未知教室') {
|
|
|
- position = extractClassroom(block.innerText);
|
|
|
+ if (!token) {
|
|
|
+ const m = document.cookie.match(/Admin-Token=([^;]+)/);
|
|
|
+ if (m) { token = m[1]; source = 'cookie[Admin-Token]'; }
|
|
|
}
|
|
|
-
|
|
|
- // 节次映射修正:按行索引重新定义startSection和endSection
|
|
|
- let startSection, endSection;
|
|
|
- switch(rowIndex) {
|
|
|
- case 0: // 第1行:第1节
|
|
|
- startSection = 1;
|
|
|
- endSection = 1;
|
|
|
- break;
|
|
|
- case 1: // 第2行:第2节
|
|
|
- startSection = 2;
|
|
|
- endSection = 2;
|
|
|
- break;
|
|
|
- case 2: // 第3行:第3节
|
|
|
- startSection = 3;
|
|
|
- endSection = 3;
|
|
|
- break;
|
|
|
- case 3: // 第4行:第4节
|
|
|
- startSection = 4;
|
|
|
- endSection = 4;
|
|
|
- break;
|
|
|
- case 4: // 第5行:第5节
|
|
|
- startSection = 5;
|
|
|
- endSection = 5;
|
|
|
- break;
|
|
|
- case 5: // 第6行:第6节
|
|
|
- startSection = 6;
|
|
|
- endSection = 6;
|
|
|
- break;
|
|
|
- case 6: // 第7行:第7节
|
|
|
- startSection = 7;
|
|
|
- endSection = 7;
|
|
|
- break;
|
|
|
- case 7: // 第8行:第8节
|
|
|
- startSection = 8;
|
|
|
- endSection = 8;
|
|
|
- break;
|
|
|
- case 8: // 第9行:第9节
|
|
|
- startSection = 9;
|
|
|
- endSection = 9;
|
|
|
- break;
|
|
|
- case 9: // 第10行:第10节
|
|
|
- startSection = 10;
|
|
|
- endSection = 10;
|
|
|
- break;
|
|
|
- case 10: // 第11行:第11节
|
|
|
- startSection = 11;
|
|
|
- endSection = 11;
|
|
|
- break;
|
|
|
- default: // 默认第1节
|
|
|
- startSection = 1;
|
|
|
- endSection = 1;
|
|
|
+ if (!token && window.token) { token = window.token; source = 'window.token'; }
|
|
|
+ if (!token && window.userToken) { token = window.userToken; source = 'window.userToken'; }
|
|
|
+
|
|
|
+ const preview = token
|
|
|
+ ? (token.length > 24 ? token.slice(0, 12) + '...' + token.slice(-8) : token)
|
|
|
+ : 'null';
|
|
|
+ return 'token=' + preview + ' (长度=' + (token ? token.length : 0) + ', source=' + source + ')\nURL=' + window.location.href;
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchSemesterList() {
|
|
|
+ console.log("拉取学期列表...");
|
|
|
+ const token = getAuthToken();
|
|
|
+ if (!token) {
|
|
|
+ throw new Error('无法获取认证 token\n\n诊断:\n' + getTokenDiagnostic());
|
|
|
}
|
|
|
-
|
|
|
- // 检查是否有rowspan(连堂课程)- 修正连堂节次计算逻辑
|
|
|
- const td = block.closest('td');
|
|
|
- if (td) {
|
|
|
- const rowspan = td.getAttribute('rowspan');
|
|
|
- if (rowspan) {
|
|
|
- const span = parseInt(rowspan);
|
|
|
- if (span > 1) {
|
|
|
- // 连堂时,结束节次 = 开始节次 + 跨行数 - 1
|
|
|
- endSection = startSection + span - 1;
|
|
|
- // 限制节次最大值不超过11
|
|
|
- if (endSection > 11) endSection = 11;
|
|
|
+
|
|
|
+ // cURL 抓包:POST 无 body(Content-Length: 0),无 Content-Type
|
|
|
+ let res;
|
|
|
+ try {
|
|
|
+ res = await fetch(API_BASE + '/api/semester/list', {
|
|
|
+ method: 'POST',
|
|
|
+ credentials: 'include',
|
|
|
+ headers: {
|
|
|
+ 'Accept': 'application/json, text/plain, */*',
|
|
|
+ 'X-Token': token
|
|
|
}
|
|
|
- }
|
|
|
+ });
|
|
|
+ } catch (fetchErr) {
|
|
|
+ // 网络层失败:CORS、URL 不可达、SSL 错误等
|
|
|
+ throw new Error('fetch 失败: ' + fetchErr.message + '\n\n诊断:\n' + getTokenDiagnostic());
|
|
|
}
|
|
|
-
|
|
|
- // 只有提取到有效的课程名称才返回
|
|
|
- if (!name || name.includes('节') || name.length < 2 || name.includes('理论课')) {
|
|
|
- return null;
|
|
|
+ if (!res.ok) {
|
|
|
+ throw new Error('semester/list HTTP ' + res.status + '\n\n诊断:\n' + getTokenDiagnostic());
|
|
|
}
|
|
|
-
|
|
|
- // 特殊处理体育课(可能没有教室)
|
|
|
- if (name.includes('体育') && position === '未知教室') {
|
|
|
- position = '操场';
|
|
|
+ const json = await res.json();
|
|
|
+ if (json.code !== 200) {
|
|
|
+ throw new Error('semester/list 返回错误 [code=' + json.code + ']: ' + json.msg + '\n\n诊断:\n' + getTokenDiagnostic());
|
|
|
}
|
|
|
-
|
|
|
- const course = {
|
|
|
- name: name,
|
|
|
- teacher: teacher || '未知教师',
|
|
|
- position: position || '未知教室',
|
|
|
- day: day,
|
|
|
- startSection: startSection,
|
|
|
- endSection: endSection,
|
|
|
- weeks: weeks.length > 0 ? weeks : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
|
|
|
- isCustomTime: false
|
|
|
- };
|
|
|
-
|
|
|
- return course;
|
|
|
+ return json.data || [];
|
|
|
}
|
|
|
-/**
|
|
|
- * 解析周次和教室
|
|
|
- */
|
|
|
-function parseWeekAndPosition(text) {
|
|
|
- let weeks = [];
|
|
|
- let position = '未知教室';
|
|
|
-
|
|
|
- if (!text) return { weeks, position };
|
|
|
-
|
|
|
- console.log("解析周次和教室:", text);
|
|
|
-
|
|
|
- // 匹配周次模式:如 "3-16周"、"1-12周"、"6-8周"
|
|
|
- const weekPattern = /(\d+-\d+周|\d+,\d+周|\d+周)/;
|
|
|
- const weekMatch = text.match(weekPattern);
|
|
|
-
|
|
|
- if (weekMatch) {
|
|
|
- const weekStr = weekMatch[1];
|
|
|
- weeks = parseWeeks(weekStr);
|
|
|
-
|
|
|
- // 剩余部分可能是教室
|
|
|
- let remaining = text.replace(weekStr, '').trim();
|
|
|
-
|
|
|
- // 如果剩余部分包含数字,很可能是教室
|
|
|
- if (remaining && /\d+/.test(remaining)) {
|
|
|
- position = remaining;
|
|
|
- } else {
|
|
|
- // 尝试从原文本中提取教室(通常是4位数字)
|
|
|
- const roomMatch = text.match(/\b\d{3,4}\b/);
|
|
|
- if (roomMatch) {
|
|
|
- position = roomMatch[0];
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 如果没有周次信息,直接尝试提取教室
|
|
|
- const roomMatch = text.match(/\b\d{3,4}\b/);
|
|
|
- if (roomMatch) {
|
|
|
- position = roomMatch[0];
|
|
|
- weeks = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
|
|
|
- }
|
|
|
+
|
|
|
+async function fetchTeachTaskList(yearTermId) {
|
|
|
+ console.log("拉取教学任务... yearTermId=" + (yearTermId || '(未指定)'));
|
|
|
+ const token = getAuthToken();
|
|
|
+ if (!token) {
|
|
|
+ throw new Error('无法获取认证 token,请确认已登录教务系统');
|
|
|
+ }
|
|
|
+ const studId = getStudentId() || '';
|
|
|
+
|
|
|
+ // body 格式来自 cURL 抓包:{"weekNum":"","yearTermId":"20252","studId":"202402417"}
|
|
|
+ let res;
|
|
|
+ try {
|
|
|
+ res = await fetch(API_BASE + '/api/teachTask/list', {
|
|
|
+ method: 'POST',
|
|
|
+ credentials: 'include',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'X-Token': token,
|
|
|
+ 'X-Requested-With': 'XMLHttpRequest'
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ weekNum: "",
|
|
|
+ yearTermId: yearTermId || "",
|
|
|
+ studId: studId
|
|
|
+ })
|
|
|
+ });
|
|
|
+ } catch (fetchErr) {
|
|
|
+ throw new Error('fetch 失败: ' + fetchErr.message + '\n\n诊断:\n' + getTokenDiagnostic());
|
|
|
}
|
|
|
-
|
|
|
- // 清理教室字符串
|
|
|
- if (position && position !== '未知教室') {
|
|
|
- // 只保留数字
|
|
|
- position = position.replace(/[^\d]/g, '');
|
|
|
+ if (!res.ok) {
|
|
|
+ throw new Error('teachTask/list HTTP ' + res.status);
|
|
|
}
|
|
|
-
|
|
|
- return { weeks, position };
|
|
|
+ const json = await res.json();
|
|
|
+ if (json.code !== 200) {
|
|
|
+ throw new Error('teachTask/list 返回错误 [code=' + json.code + ']: ' + json.msg);
|
|
|
+ }
|
|
|
+ return json.data || [];
|
|
|
}
|
|
|
+
|
|
|
+// ==================== 数据转换 ====================
|
|
|
/**
|
|
|
- * 从文本中提取教室
|
|
|
+ * 把选中的学期对象转成内部用的 semesterInfo
|
|
|
+ * semesterStartDate 来自学期 beginDate
|
|
|
+ * semesterTotalWeeks 来自 beginWeek..endWeek
|
|
|
*/
|
|
|
-function extractClassroom(text) {
|
|
|
- // 匹配常见的教室格式:1205、6604、1231、2303、6403、6212、2103、1233、6104、1203
|
|
|
- const roomPatterns = [
|
|
|
- /\b\d{4}\b/, // 4位数字
|
|
|
- /\b\d{3}\b/, // 3位数字
|
|
|
- /[0-9]{3,4}/ // 任意3-4位数字
|
|
|
- ];
|
|
|
-
|
|
|
- for (let pattern of roomPatterns) {
|
|
|
- const match = text.match(pattern);
|
|
|
- if (match) {
|
|
|
- return match[0];
|
|
|
- }
|
|
|
+function buildSemesterInfoFromSemester(semester) {
|
|
|
+ if (!semester) {
|
|
|
+ return { semesterStartDate: '2026-09-07', semesterTotalWeeks: 25, yearTermId: null };
|
|
|
}
|
|
|
-
|
|
|
- return '未知教室';
|
|
|
+ const beginWeek = semester.beginWeek || 1;
|
|
|
+ const endWeek = semester.endWeek || 25;
|
|
|
+ return {
|
|
|
+ semesterStartDate: semester.beginDate,
|
|
|
+ semesterTotalWeeks: endWeek - beginWeek + 1,
|
|
|
+ yearTermId: semester.yearTermID
|
|
|
+ };
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
- * 解析周次字符串
|
|
|
+ * 根据 lessType 把 weekBegin..weekEnd 展开为周次数组
|
|
|
+ * "全周" → 所有周
|
|
|
+ * "单周" → 仅奇数周
|
|
|
+ * "双周" → 仅偶数周
|
|
|
*/
|
|
|
-function parseWeeks(weekStr) {
|
|
|
+function buildWeeksArray(weekBegin, weekEnd, lessType) {
|
|
|
const weeks = [];
|
|
|
- if (!weekStr) return [];
|
|
|
-
|
|
|
- // 移除"周"字
|
|
|
- weekStr = weekStr.replace(/周/g, '').trim();
|
|
|
-
|
|
|
- try {
|
|
|
- if (weekStr.includes(',')) {
|
|
|
- weekStr.split(',').forEach(part => {
|
|
|
- if (part.includes('-')) {
|
|
|
- const [start, end] = part.split('-').map(Number);
|
|
|
- for (let i = start; i <= end; i++) weeks.push(i);
|
|
|
- } else {
|
|
|
- const w = parseInt(part);
|
|
|
- if (!isNaN(w)) weeks.push(w);
|
|
|
- }
|
|
|
- });
|
|
|
- } else if (weekStr.includes('-')) {
|
|
|
- const [start, end] = weekStr.split('-').map(Number);
|
|
|
- for (let i = start; i <= end; i++) weeks.push(i);
|
|
|
- } else {
|
|
|
- const w = parseInt(weekStr);
|
|
|
- if (!isNaN(w)) weeks.push(w);
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error("解析周次失败:", weekStr, e);
|
|
|
+ const start = weekBegin || 1;
|
|
|
+ const end = weekEnd || start;
|
|
|
+ for (let w = start; w <= end; w++) {
|
|
|
+ if (lessType === '单周' && w % 2 === 0) continue;
|
|
|
+ if (lessType === '双周' && w % 2 === 1) continue;
|
|
|
+ weeks.push(w);
|
|
|
}
|
|
|
-
|
|
|
- return weeks.length > 0 ? weeks.sort((a,b) => a-b) : [];
|
|
|
+ return weeks;
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
- * 去重
|
|
|
+ * 把 teachTask/list 返回的数组转换成 saveImportedCourses 所需格式
|
|
|
+ * 注意:day 直接用 API 的 weekDay(1=周一 ... 7=周日)
|
|
|
+ * 若发现课程排错星期,改成 task.weekDay - 1(0=周一)即可
|
|
|
*/
|
|
|
-function removeDuplicates(courses) {
|
|
|
- const seen = new Map();
|
|
|
- return courses.filter(course => {
|
|
|
- // 创建唯一键:课程名+教师+星期+开始节次
|
|
|
- const key = `${course.name}-${course.teacher}-${course.day}-${course.startSection}`;
|
|
|
- if (seen.has(key)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- seen.set(key, true);
|
|
|
- return true;
|
|
|
+function transformTeachTaskToCourses(taskList) {
|
|
|
+ const courses = [];
|
|
|
+ (taskList || []).forEach(task => {
|
|
|
+ // 教师名拼接(多个教师用「、」连接)
|
|
|
+ const teachers = (task.teacherList || [])
|
|
|
+ .map(t => t.teacherName)
|
|
|
+ .filter(name => name && String(name).trim().length > 0);
|
|
|
+ const teacher = teachers.length > 0 ? teachers.join('、') : '未知教师';
|
|
|
+
|
|
|
+ const course = {
|
|
|
+ name: task.courName || '未知课程',
|
|
|
+ teacher: teacher,
|
|
|
+ position: task.roomId ? String(task.roomId) : '未知教室',
|
|
|
+ day: task.weekDay, // API: 1=周一 ... 7=周日
|
|
|
+ startSection: task.startSection,
|
|
|
+ endSection: task.endSection,
|
|
|
+ weeks: buildWeeksArray(task.weekBegin, task.weekEnd, task.lessType),
|
|
|
+ isCustomTime: false
|
|
|
+ };
|
|
|
+ courses.push(course);
|
|
|
});
|
|
|
-}
|
|
|
-/**
|
|
|
- * 提取学期信息
|
|
|
- */
|
|
|
-function extractSemesterInfo() {
|
|
|
- return {
|
|
|
- semesterStartDate: "2026-03-01",
|
|
|
- semesterTotalWeeks: 20
|
|
|
- };
|
|
|
+ return courses;
|
|
|
}
|
|
|
|
|
|
// ========== 根据季节获取时间段(夏季/秋季) ==========
|
|
|
@@ -458,7 +257,7 @@ function getSeasonTimeSlots(season) {
|
|
|
{"number": 3, "startTime": "10:05", "endTime": "10:50"},
|
|
|
{"number": 4, "startTime": "10:55", "endTime": "11:40"}
|
|
|
];
|
|
|
-
|
|
|
+
|
|
|
// 夏季下午/晚上时间
|
|
|
const summer_afternoon_evening = [
|
|
|
{"number": 5, "startTime": "14:30", "endTime": "15:15"},
|
|
|
@@ -469,7 +268,7 @@ function getSeasonTimeSlots(season) {
|
|
|
{"number": 10, "startTime": "19:35", "endTime": "20:20"},
|
|
|
{"number": 11, "startTime": "20:25", "endTime": "21:10"}
|
|
|
];
|
|
|
-
|
|
|
+
|
|
|
// 秋季下午/晚上时间
|
|
|
const autumn_afternoon_evening = [
|
|
|
{"number": 5, "startTime": "14:00", "endTime": "14:45"},
|
|
|
@@ -481,15 +280,11 @@ function getSeasonTimeSlots(season) {
|
|
|
{"number": 11, "startTime": "19:55", "endTime": "20:40"}
|
|
|
];
|
|
|
|
|
|
- if (season === 'summer') {
|
|
|
- return morning_classes.concat(summer_afternoon_evening);
|
|
|
- } else if (season === 'autumn') {
|
|
|
+ if (season === 'autumn') {
|
|
|
return morning_classes.concat(autumn_afternoon_evening);
|
|
|
- } else {
|
|
|
- // 默认返回夏季(兼容旧调用)
|
|
|
- console.warn("未知季节参数,使用夏季时间");
|
|
|
- return morning_classes.concat(summer_afternoon_evening);
|
|
|
}
|
|
|
+ // 默认夏季(兼容旧调用)
|
|
|
+ return morning_classes.concat(summer_afternoon_evening);
|
|
|
}
|
|
|
|
|
|
// ==================== 弹窗和导入函数 ====================
|
|
|
@@ -497,9 +292,9 @@ async function demoAlert() {
|
|
|
try {
|
|
|
const confirmed = await window.shiguangBridgePromise.showAlert(
|
|
|
"📚 湖北汽车工业学院课表导入",
|
|
|
- "将提取当前页面的课表数据并导入到App\n\n" +
|
|
|
- "📌 请确认已在课表页面\n" +
|
|
|
- "📌 将提取所有可见课程",
|
|
|
+ "将通过教务系统 API 拉取课表数据并导入到 App\n\n" +
|
|
|
+ "📌 请确认已登录教务系统\n" +
|
|
|
+ "📌 流程:选择学期 → 选择季节 → 拉取课程",
|
|
|
"开始导入",
|
|
|
"取消"
|
|
|
);
|
|
|
@@ -509,242 +304,289 @@ async function demoAlert() {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
-async function demoPrompt() {
|
|
|
+
|
|
|
+async function demoPrompt(semesterInfo) {
|
|
|
try {
|
|
|
- const semesterInfo = extractSemesterInfo();
|
|
|
+ const defaultDate = (semesterInfo && semesterInfo.semesterStartDate) || '2026-09-07';
|
|
|
const semesterStart = await window.shiguangBridgePromise.showPrompt(
|
|
|
"📅 设置开学日期",
|
|
|
- "请输入本学期开学日期",
|
|
|
- semesterInfo.semesterStartDate,
|
|
|
+ "已从教务系统获取开学日期,如需修改请直接编辑",
|
|
|
+ defaultDate,
|
|
|
"validateDate"
|
|
|
);
|
|
|
- return semesterStart || semesterInfo.semesterStartDate;
|
|
|
+ return semesterStart || defaultDate;
|
|
|
} catch (error) {
|
|
|
console.error("日期输入错误:", error);
|
|
|
- return "2026-03-01";
|
|
|
+ return "2026-09-07";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 学期选择 - 用单选列表让用户选学期
|
|
|
+ * 默认选中 currYearTerm === 1 的当前学期
|
|
|
+ */
|
|
|
+async function pickSemester(semesterList) {
|
|
|
+ if (!semesterList || semesterList.length === 0) return null;
|
|
|
+
|
|
|
+ const names = semesterList.map(s => s.yearTermName || s.yearTermID);
|
|
|
+ // 默认选中当前学期(currYearTerm === 1)
|
|
|
+ let defaultIdx = semesterList.findIndex(s => s.currYearTerm === 1);
|
|
|
+ if (defaultIdx < 0) defaultIdx = 0;
|
|
|
+
|
|
|
+ try {
|
|
|
+ console.log("即将显示学期选择弹窗...");
|
|
|
+ const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
+ "选择要导入的学期",
|
|
|
+ JSON.stringify(names),
|
|
|
+ defaultIdx
|
|
|
+ );
|
|
|
+
|
|
|
+ if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < semesterList.length) {
|
|
|
+ const picked = semesterList[selectedIndex];
|
|
|
+ console.log("用户选择了: " + picked.yearTermName + " (yearTermID=" + picked.yearTermID + ")");
|
|
|
+ window.shiguangBridge.showToast("已选 " + picked.yearTermName);
|
|
|
+ return picked;
|
|
|
+ }
|
|
|
+ console.log("用户取消了学期选择。");
|
|
|
+ window.shiguangBridge.showToast("学期选择:用户取消");
|
|
|
+ return null;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("显示学期选择弹窗出错:", error);
|
|
|
+ window.shiguangBridge.showToast("学期选择出错:" + error.message);
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 导入时间段 - 让用户选择夏季或秋季
|
|
|
- * 修复:使用更可靠的方式来处理两个选项
|
|
|
+ * 导入时间段 - 使用单选列表弹窗让用户选择夏季或秋季
|
|
|
+ * 参考 demoSingleSelection 的调用模式
|
|
|
*/
|
|
|
async function importPresetTimeSlots() {
|
|
|
- console.log("正在准备预设时间段数据...");
|
|
|
-
|
|
|
- // 修复:使用showConfirmDialog而不是showAlert来确保两个按钮都显示
|
|
|
+ const seasons = ["夏季", "秋季"];
|
|
|
try {
|
|
|
- // 尝试使用showConfirmDialog(如果有的话)
|
|
|
- if (window.shiguangBridgePromise && typeof window.shiguangBridgePromise.showConfirmDialog === 'function') {
|
|
|
- const seasonChoice = await window.shiguangBridgePromise.showConfirmDialog(
|
|
|
- "⏰ 选择作息时间",
|
|
|
- "请根据当前学期选择作息时间:\n\n🌞 夏季(5月1日-9月30日)\n🍂 秋季(10月1日-次年4月30日)",
|
|
|
- "夏季",
|
|
|
- "秋季"
|
|
|
- );
|
|
|
-
|
|
|
- // showConfirmDialog 点击左侧按钮返回 true,点击右侧按钮返回 false
|
|
|
- let season = seasonChoice === true ? 'summer' : 'autumn';
|
|
|
-
|
|
|
- // 获取对应季节的时间段
|
|
|
- const presetTimeSlots = getSeasonTimeSlots(season);
|
|
|
- console.log(`选择的季节: ${season},时间段:`, presetTimeSlots);
|
|
|
-
|
|
|
- // 导入时间段
|
|
|
- const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
|
|
|
- if (result === true) {
|
|
|
- console.log("预设时间段导入成功!");
|
|
|
- window.shiguangBridge.showToast(`✅ ${season === 'summer' ? '夏季' : '秋季'}时间段导入成功!`);
|
|
|
- return true;
|
|
|
- } else {
|
|
|
- console.log("预设时间段导入未成功,结果:" + result);
|
|
|
- window.shiguangBridge.showToast("❌ 时间段导入失败,请查看日志。");
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
- // 使用showAlert但确保两个按钮都显示
|
|
|
- else {
|
|
|
- // 方法1:尝试使用showAlert两个按钮 - 修复按钮显示问题
|
|
|
- // 注意:有些AndroidBridge实现中,showAlert的第三个参数是左侧按钮,第四个参数是右侧按钮
|
|
|
- // 确保两个按钮文本都不为空
|
|
|
- const seasonChoice = await window.shiguangBridgePromise.showAlert(
|
|
|
- "⏰ 选择作息时间",
|
|
|
- "请根据当前学期选择作息时间:\n\n🌞 夏季(5月1日-9月30日)\n🍂 秋季(10月1日-次年4月30日)",
|
|
|
- "夏季", // 左侧按钮
|
|
|
- "秋季" // 右侧按钮
|
|
|
- );
|
|
|
-
|
|
|
- // 根据返回值判断用户点击了哪个按钮
|
|
|
- // showAlert 点击左侧按钮返回 true,点击右侧按钮返回 false
|
|
|
- let season = 'summer';
|
|
|
-
|
|
|
- // 更精确地判断返回值
|
|
|
- if (seasonChoice === true || seasonChoice === "true" || seasonChoice === 1) {
|
|
|
- season = 'summer';
|
|
|
- console.log("用户选择了夏季");
|
|
|
- } else if (seasonChoice === false || seasonChoice === "false" || seasonChoice === 0) {
|
|
|
- season = 'autumn';
|
|
|
- console.log("用户选择了秋季");
|
|
|
- } else if (typeof seasonChoice === 'string') {
|
|
|
- // 如果返回的是按钮文本
|
|
|
- if (seasonChoice === '夏季') {
|
|
|
- season = 'summer';
|
|
|
- } else {
|
|
|
- season = 'autumn';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ console.log("即将显示季节选择单选列表弹窗...");
|
|
|
+ const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
+ "选择作息时间",
|
|
|
+ JSON.stringify(seasons),
|
|
|
+ 0
|
|
|
+ );
|
|
|
+ if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < seasons.length) {
|
|
|
+ console.log("用户选择了: " + seasons[selectedIndex] + " (索引: " + selectedIndex + ")");
|
|
|
+ window.shiguangBridge.showToast("你选择了 " + seasons[selectedIndex]);
|
|
|
+
|
|
|
+ // 索引 0 -> 夏季,索引 1 -> 秋季
|
|
|
+ const season = selectedIndex === 0 ? 'summer' : 'autumn';
|
|
|
+
|
|
|
// 获取对应季节的时间段
|
|
|
const presetTimeSlots = getSeasonTimeSlots(season);
|
|
|
console.log(`选择的季节: ${season},时间段:`, presetTimeSlots);
|
|
|
-
|
|
|
+
|
|
|
// 导入时间段
|
|
|
const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
|
|
|
if (result === true) {
|
|
|
console.log("预设时间段导入成功!");
|
|
|
- window.shiguangBridge.showToast(`✅ ${season === 'summer' ? '夏季' : '秋季'}时间段导入成功!`);
|
|
|
- return true;
|
|
|
+ window.shiguangBridge.showToast(`✅ ${seasons[selectedIndex]}时间段导入成功!`);
|
|
|
+ return true; // 成功时返回 true
|
|
|
} else {
|
|
|
console.log("预设时间段导入未成功,结果:" + result);
|
|
|
window.shiguangBridge.showToast("❌ 时间段导入失败,请查看日志。");
|
|
|
return false;
|
|
|
}
|
|
|
+ } else {
|
|
|
+ console.log("用户取消了选择。");
|
|
|
+ window.shiguangBridge.showToast("季节选择:用户取消了选择!");
|
|
|
+ return false; // 用户取消时返回 false
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error("导入时间段时发生错误:", error);
|
|
|
-
|
|
|
- // 方法2:如果两个按钮的方法失败,尝试使用showPrompt让用户输入
|
|
|
- try {
|
|
|
- console.log("尝试使用备用方法选择季节...");
|
|
|
- const seasonInput = await window.shiguangBridgePromise.showPrompt(
|
|
|
- "⏰ 选择作息时间",
|
|
|
- "请输入季节(输入 1 选择夏季,输入 2 选择秋季):\n\n1 - 夏季 (5月1日-9月30日)\n2 - 秋季 (10月1日-次年4月30日)",
|
|
|
- "1"
|
|
|
- );
|
|
|
-
|
|
|
- let season = 'summer';
|
|
|
- if (seasonInput === '2') {
|
|
|
- season = 'autumn';
|
|
|
- }
|
|
|
-
|
|
|
- const presetTimeSlots = getSeasonTimeSlots(season);
|
|
|
- const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
|
|
|
-
|
|
|
- if (result === true) {
|
|
|
- window.shiguangBridge.showToast(`✅ ${season === 'summer' ? '夏季' : '秋季'}时间段导入成功!`);
|
|
|
- return true;
|
|
|
- }
|
|
|
- } catch (secondError) {
|
|
|
- console.error("备用方法也失败:", secondError);
|
|
|
- window.shiguangBridge.showToast("导入时间段失败,使用默认夏季时间");
|
|
|
-
|
|
|
- // 方法3:使用默认夏季
|
|
|
- const defaultTimeSlots = getSeasonTimeSlots('summer');
|
|
|
- await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(defaultTimeSlots));
|
|
|
- window.shiguangBridge.showToast("✅ 默认夏季时间段导入成功");
|
|
|
- return true;
|
|
|
- }
|
|
|
+ console.error("显示季节选择单选列表弹窗时发生错误:", error);
|
|
|
+ window.shiguangBridge.showToast("季节选择:显示列表出错!" + error.message);
|
|
|
+ return false; // 出现错误时也返回 false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function importSchedule() {
|
|
|
+/**
|
|
|
+ * 从 API 拉取课程并导入(按选中的 yearTermId 过滤)
|
|
|
+ */
|
|
|
+async function importSchedule(semesterInfo) {
|
|
|
+ const result = { success: false, courseCount: 0, errorMsg: null };
|
|
|
try {
|
|
|
- window.shiguangBridge.showToast("正在提取课表数据...");
|
|
|
-
|
|
|
- const courses = extractCourseData();
|
|
|
-
|
|
|
+ window.shiguangBridge.showToast("正在从教务系统拉取课表...");
|
|
|
+
|
|
|
+ const yearTermId = semesterInfo && semesterInfo.yearTermId;
|
|
|
+ const tasks = await fetchTeachTaskList(yearTermId);
|
|
|
+ console.log(`拉取到 ${tasks.length} 条教学任务`);
|
|
|
+
|
|
|
+ const courses = transformTeachTaskToCourses(tasks);
|
|
|
+ result.courseCount = courses.length;
|
|
|
+
|
|
|
if (courses.length === 0) {
|
|
|
+ result.errorMsg = "教务系统未返回任何课程数据";
|
|
|
await window.shiguangBridgePromise.showAlert(
|
|
|
- "⚠️ 提取失败",
|
|
|
- "未找到课表数据,请确认已在课表页面",
|
|
|
+ "⚠️ 拉取失败",
|
|
|
+ result.errorMsg + ",请确认已登录且所选学期有课程",
|
|
|
"知道了"
|
|
|
);
|
|
|
- return false;
|
|
|
+ return result;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 预览
|
|
|
const preview = await window.shiguangBridgePromise.showAlert(
|
|
|
"📊 数据预览",
|
|
|
- `共找到 ${courses.length} 门课程\n\n` +
|
|
|
- `示例:\n${courses.slice(0, 5).map(c =>
|
|
|
- `• 周${c.day+1} ${c.name} - 第${c.startSection}-${c.endSection}节`
|
|
|
+ `共拉取到 ${courses.length} 条课程记录\n\n` +
|
|
|
+ `示例:\n${courses.slice(0, 5).map(c =>
|
|
|
+ `• 周${c.day} ${c.name} - 第${c.startSection}-${c.endSection}节`
|
|
|
).join('\n')}`,
|
|
|
"确认导入",
|
|
|
"取消"
|
|
|
);
|
|
|
-
|
|
|
+
|
|
|
if (!preview) {
|
|
|
- return false;
|
|
|
+ result.errorMsg = "用户在预览时取消";
|
|
|
+ return result;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 导入课程
|
|
|
window.shiguangBridge.showToast("正在导入课程...");
|
|
|
- const result = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
-
|
|
|
- if (result === true) {
|
|
|
- const semesterDate = await demoPrompt();
|
|
|
- const configResult = await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({
|
|
|
- semesterStartDate: semesterDate,
|
|
|
- semesterTotalWeeks: 20,
|
|
|
- defaultClassDuration: 45,
|
|
|
- defaultBreakDuration: 10,
|
|
|
- firstDayOfWeek: 1
|
|
|
- }));
|
|
|
-
|
|
|
- if (configResult === true) {
|
|
|
- window.shiguangBridge.showToast(`✅ 导入成功!共${courses.length}门课程`);
|
|
|
- return true;
|
|
|
- }
|
|
|
+ const saveResult = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
+
|
|
|
+ if (saveResult !== true) {
|
|
|
+ result.errorMsg = "saveImportedCourses 返回非 true";
|
|
|
+ return result;
|
|
|
}
|
|
|
-
|
|
|
- window.shiguangBridge.showToast("❌ 导入失败");
|
|
|
- return false;
|
|
|
-
|
|
|
+
|
|
|
+ // 设置开学日期(默认值来自学期 API 的 beginDate)
|
|
|
+ const semesterDate = await demoPrompt(semesterInfo);
|
|
|
+ const totalWeeks = (semesterInfo && semesterInfo.semesterTotalWeeks) || 25;
|
|
|
+
|
|
|
+ // 保存课表配置
|
|
|
+ const configResult = await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({
|
|
|
+ semesterStartDate: semesterDate,
|
|
|
+ semesterTotalWeeks: totalWeeks,
|
|
|
+ defaultClassDuration: 45,
|
|
|
+ defaultBreakDuration: 10,
|
|
|
+ firstDayOfWeek: 1
|
|
|
+ }));
|
|
|
+
|
|
|
+ if (configResult === true) {
|
|
|
+ result.success = true;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ result.errorMsg = "saveCourseConfig 返回非 true";
|
|
|
+ return result;
|
|
|
} catch (error) {
|
|
|
console.error("导入错误:", error);
|
|
|
- window.shiguangBridge.showToast("导入出错: " + error.message);
|
|
|
- return false;
|
|
|
+ result.errorMsg = error.message;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 显示最终结果汇总弹窗
|
|
|
+ */
|
|
|
+async function showFinalResult(results) {
|
|
|
+ const success = results.schedule && results.schedule.success;
|
|
|
+ const title = success ? '🎉 导入成功' : '❌ 导入失败';
|
|
|
+
|
|
|
+ let msg = '';
|
|
|
+ if (results.semesterName) msg += '📚 学期: ' + results.semesterName + '\n';
|
|
|
+ if (results.schedule && results.schedule.courseCount > 0) {
|
|
|
+ msg += '📖 课程: ' + results.schedule.courseCount + ' 条\n';
|
|
|
+ }
|
|
|
+ msg += '\n📋 步骤状态:\n';
|
|
|
+ msg += (results.semesterList ? '✅' : '❌') + ' 获取学期列表\n';
|
|
|
+ msg += (results.season ? '✅' : '❌') + ' 导入时间段';
|
|
|
+ if (results.seasonName) msg += ' (' + results.seasonName + ')';
|
|
|
+ msg += '\n';
|
|
|
+ msg += (results.schedule ? (results.schedule.success ? '✅' : '❌') : '⏭️') + ' 导入课表';
|
|
|
+ if (results.schedule && results.schedule.courseCount > 0) msg += ' (' + results.schedule.courseCount + '条)';
|
|
|
+ msg += '\n';
|
|
|
+
|
|
|
+ if (results.errorMsg) {
|
|
|
+ msg += '\n❗ 错误: ' + results.errorMsg;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await window.shiguangBridgePromise.showAlert(title, msg, '知道了');
|
|
|
+ } catch (e) {
|
|
|
+ console.error('显示最终结果弹窗失败:', e);
|
|
|
+ window.shiguangBridge.showToast(title);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function runAllDemosSequentially() {
|
|
|
window.shiguangBridge.showToast("🚀 课表导入助手启动...");
|
|
|
-
|
|
|
- // 检查页面
|
|
|
- if (!window.location.href.includes('studentHome/expectCourseTable')) {
|
|
|
- const goToPage = await window.shiguangBridgePromise.showAlert(
|
|
|
- "页面提示",
|
|
|
- "当前不在课表页面,是否跳转?",
|
|
|
- "跳转",
|
|
|
- "取消"
|
|
|
- );
|
|
|
- if (goToPage) {
|
|
|
- window.location.href = 'http://neweas.huat.edu.cn/#/studentHome/expectCourseTable';
|
|
|
+
|
|
|
+ const results = {
|
|
|
+ semesterList: false,
|
|
|
+ semesterName: '',
|
|
|
+ season: false,
|
|
|
+ seasonName: '',
|
|
|
+ schedule: null,
|
|
|
+ errorMsg: null
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 确认开始
|
|
|
+ const start = await demoAlert();
|
|
|
+ if (!start) {
|
|
|
+ results.errorMsg = '用户取消(开始按钮)';
|
|
|
+ await showFinalResult(results);
|
|
|
+ return;
|
|
|
}
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const start = await demoAlert();
|
|
|
- if (!start) {
|
|
|
- window.shiguangBridge.showToast("已取消");
|
|
|
- return;
|
|
|
+
|
|
|
+ // 2. 拉学期列表
|
|
|
+ let semesterList = [];
|
|
|
+ try {
|
|
|
+ window.shiguangBridge.showToast("正在获取学期列表...");
|
|
|
+ semesterList = await fetchSemesterList();
|
|
|
+ console.log(`拉取到 ${semesterList.length} 个学期`);
|
|
|
+ results.semesterList = true;
|
|
|
+ } catch (e) {
|
|
|
+ console.error("获取学期列表失败:", e);
|
|
|
+ results.errorMsg = e.message;
|
|
|
+ await showFinalResult(results);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 让用户选择学期
|
|
|
+ const pickedSemester = await pickSemester(semesterList);
|
|
|
+ if (!pickedSemester) {
|
|
|
+ results.errorMsg = '用户取消了学期选择';
|
|
|
+ await showFinalResult(results);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ results.semesterName = pickedSemester.yearTermName;
|
|
|
+ const semesterInfo = buildSemesterInfoFromSemester(pickedSemester);
|
|
|
+
|
|
|
+ // 4. 导入时间段(季节选择:夏季/秋季)
|
|
|
+ results.season = await importPresetTimeSlots();
|
|
|
+ // 季节名映射(与 importPresetTimeSlots 内部一致)
|
|
|
+ // 这里不直接拿名字,留空也行;如果想显示需要改 importPresetTimeSlots 返回值
|
|
|
+
|
|
|
+ // 5. 导入课表(用选中的 yearTermId 拉 teachTask)
|
|
|
+ results.schedule = await importSchedule(semesterInfo);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("运行错误:", e);
|
|
|
+ results.errorMsg = e.message;
|
|
|
}
|
|
|
-
|
|
|
- // 导入时间段(带季节选择:夏季/秋季)
|
|
|
- await importPresetTimeSlots();
|
|
|
-
|
|
|
- // 导入课表
|
|
|
- await importSchedule();
|
|
|
-
|
|
|
+
|
|
|
+ await showFinalResult(results);
|
|
|
window.shiguangBridge.notifyTaskCompletion();
|
|
|
}
|
|
|
|
|
|
// 导出函数
|
|
|
window.validateDate = validateDate;
|
|
|
-window.validateName = validateName;
|
|
|
-window.extractCourseData = extractCourseData;
|
|
|
-window.importPresetTimeSlots = importPresetTimeSlots;
|
|
|
+window.getAuthToken = getAuthToken;
|
|
|
+window.getStudentId = getStudentId;
|
|
|
+window.decodeJwtPayload = decodeJwtPayload;
|
|
|
+window.fetchSemesterList = fetchSemesterList;
|
|
|
+window.fetchTeachTaskList = fetchTeachTaskList;
|
|
|
+window.pickSemester = pickSemester;
|
|
|
+window.transformTeachTaskToCourses = transformTeachTaskToCourses;
|
|
|
window.getSeasonTimeSlots = getSeasonTimeSlots;
|
|
|
+window.importPresetTimeSlots = importPresetTimeSlots;
|
|
|
+window.importSchedule = importSchedule;
|
|
|
|
|
|
// 启动
|
|
|
runAllDemosSequentially();
|