HUAT.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. // 文件: HUAT.js - 湖北汽车工业学院课表导入(基于教务系统 API)
  2. // 数据源:
  3. // /api/semester/list → 可选学期列表(用于学期选择 + 学期基本信息)
  4. // /api/teachTask/list → 教学任务列表(按 yearTermId 过滤)
  5. // ==================== 验证函数 ====================
  6. function validateDate(dateStr) {
  7. if (!dateStr || dateStr.trim().length === 0) {
  8. return "日期不能为空!";
  9. }
  10. const datePattern = /^\d{4}-\d{2}-\d{2}$/;
  11. if (!datePattern.test(dateStr)) {
  12. return "日期格式必须是 YYYY-MM-DD!";
  13. }
  14. const parts = dateStr.split('-');
  15. const year = parseInt(parts[0]);
  16. const month = parseInt(parts[1]);
  17. const day = parseInt(parts[2]);
  18. const date = new Date(year, month - 1, day);
  19. if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
  20. return "请输入有效的日期!";
  21. }
  22. return false;
  23. }
  24. // ==================== API 数据获取 ====================
  25. // 用当前页面协议构造 API 基址,避免 HTTP/HTTPS 跨域
  26. // (http:// 和 https:// 在 CORS 看来是不同 origin,会触发预检并失败)
  27. const API_BASE = window.location.protocol + '//neweas.huat.edu.cn';
  28. /**
  29. * 获取认证 token(X-Token 头的值)
  30. * 优先级:localStorage / sessionStorage 常见 key → Admin-Token cookie → window 全局
  31. */
  32. function getAuthToken() {
  33. const storageKeys = ['Admin-Token', 'adminToken', 'token', 'X-Token', 'x-token', 'admin_token'];
  34. for (const key of storageKeys) {
  35. const v = localStorage.getItem(key) || sessionStorage.getItem(key);
  36. if (v) return v;
  37. }
  38. // 从 cookie 读 Admin-Token
  39. const cookieMatch = document.cookie.match(/Admin-Token=([^;]+)/);
  40. if (cookieMatch) return cookieMatch[1];
  41. // window 全局
  42. if (window.token) return window.token;
  43. if (window.userToken) return window.userToken;
  44. return null;
  45. }
  46. /**
  47. * 解析 JWT payload(仅解码,不验签)
  48. */
  49. function decodeJwtPayload(jwt) {
  50. if (!jwt) return null;
  51. const parts = jwt.split('.');
  52. if (parts.length !== 3) return null;
  53. try {
  54. const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
  55. const json = decodeURIComponent(atob(b64).split('').map(c =>
  56. '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
  57. ).join(''));
  58. return JSON.parse(json);
  59. } catch (e) {
  60. return null;
  61. }
  62. }
  63. /**
  64. * 从 JWT 的 sub 字段提取学生 ID
  65. */
  66. function getStudentId() {
  67. const token = getAuthToken();
  68. if (!token) return null;
  69. const payload = decodeJwtPayload(token);
  70. return (payload && payload.sub) || null;
  71. }
  72. /**
  73. * 诊断信息:token 来源 + 预览 + 当前页面 URL
  74. * 用于手机端排查认证失败问题(手机端通常没 DevTools 可看)
  75. */
  76. function getTokenDiagnostic() {
  77. const storageKeys = ['Admin-Token', 'adminToken', 'token', 'X-Token', 'x-token', 'admin_token'];
  78. let source = '未找到';
  79. let token = null;
  80. for (const key of storageKeys) {
  81. const v = localStorage.getItem(key);
  82. if (v) { token = v; source = 'localStorage[' + key + ']'; break; }
  83. }
  84. if (!token) {
  85. for (const key of storageKeys) {
  86. const v = sessionStorage.getItem(key);
  87. if (v) { token = v; source = 'sessionStorage[' + key + ']'; break; }
  88. }
  89. }
  90. if (!token) {
  91. const m = document.cookie.match(/Admin-Token=([^;]+)/);
  92. if (m) { token = m[1]; source = 'cookie[Admin-Token]'; }
  93. }
  94. if (!token && window.token) { token = window.token; source = 'window.token'; }
  95. if (!token && window.userToken) { token = window.userToken; source = 'window.userToken'; }
  96. const preview = token
  97. ? (token.length > 24 ? token.slice(0, 12) + '...' + token.slice(-8) : token)
  98. : 'null';
  99. return 'token=' + preview + ' (长度=' + (token ? token.length : 0) + ', source=' + source + ')\nURL=' + window.location.href;
  100. }
  101. async function fetchSemesterList() {
  102. console.log("拉取学期列表...");
  103. const token = getAuthToken();
  104. if (!token) {
  105. throw new Error('无法获取认证 token\n\n诊断:\n' + getTokenDiagnostic());
  106. }
  107. // cURL 抓包:POST 无 body(Content-Length: 0),无 Content-Type
  108. let res;
  109. try {
  110. res = await fetch(API_BASE + '/api/semester/list', {
  111. method: 'POST',
  112. credentials: 'include',
  113. headers: {
  114. 'Accept': 'application/json, text/plain, */*',
  115. 'X-Token': token
  116. }
  117. });
  118. } catch (fetchErr) {
  119. // 网络层失败:CORS、URL 不可达、SSL 错误等
  120. throw new Error('fetch 失败: ' + fetchErr.message + '\n\n诊断:\n' + getTokenDiagnostic());
  121. }
  122. if (!res.ok) {
  123. throw new Error('semester/list HTTP ' + res.status + '\n\n诊断:\n' + getTokenDiagnostic());
  124. }
  125. const json = await res.json();
  126. if (json.code !== 200) {
  127. throw new Error('semester/list 返回错误 [code=' + json.code + ']: ' + json.msg + '\n\n诊断:\n' + getTokenDiagnostic());
  128. }
  129. return json.data || [];
  130. }
  131. async function fetchTeachTaskList(yearTermId) {
  132. console.log("拉取教学任务... yearTermId=" + (yearTermId || '(未指定)'));
  133. const token = getAuthToken();
  134. if (!token) {
  135. throw new Error('无法获取认证 token,请确认已登录教务系统');
  136. }
  137. const studId = getStudentId() || '';
  138. // body 格式来自 cURL 抓包:{"weekNum":"","yearTermId":"20252","studId":"202402417"}
  139. let res;
  140. try {
  141. res = await fetch(API_BASE + '/api/teachTask/list', {
  142. method: 'POST',
  143. credentials: 'include',
  144. headers: {
  145. 'Content-Type': 'application/json',
  146. 'X-Token': token,
  147. 'X-Requested-With': 'XMLHttpRequest'
  148. },
  149. body: JSON.stringify({
  150. weekNum: "",
  151. yearTermId: yearTermId || "",
  152. studId: studId
  153. })
  154. });
  155. } catch (fetchErr) {
  156. throw new Error('fetch 失败: ' + fetchErr.message + '\n\n诊断:\n' + getTokenDiagnostic());
  157. }
  158. if (!res.ok) {
  159. throw new Error('teachTask/list HTTP ' + res.status);
  160. }
  161. const json = await res.json();
  162. if (json.code !== 200) {
  163. throw new Error('teachTask/list 返回错误 [code=' + json.code + ']: ' + json.msg);
  164. }
  165. return json.data || [];
  166. }
  167. // ==================== 数据转换 ====================
  168. /**
  169. * 把选中的学期对象转成内部用的 semesterInfo
  170. * semesterStartDate 来自学期 beginDate
  171. * semesterTotalWeeks 来自 beginWeek..endWeek
  172. */
  173. function buildSemesterInfoFromSemester(semester) {
  174. if (!semester) {
  175. return { semesterStartDate: '2026-09-07', semesterTotalWeeks: 25, yearTermId: null };
  176. }
  177. const beginWeek = semester.beginWeek || 1;
  178. const endWeek = semester.endWeek || 25;
  179. return {
  180. semesterStartDate: semester.beginDate,
  181. semesterTotalWeeks: endWeek - beginWeek + 1,
  182. yearTermId: semester.yearTermID
  183. };
  184. }
  185. /**
  186. * 根据 lessType 把 weekBegin..weekEnd 展开为周次数组
  187. * "全周" → 所有周
  188. * "单周" → 仅奇数周
  189. * "双周" → 仅偶数周
  190. */
  191. function buildWeeksArray(weekBegin, weekEnd, lessType) {
  192. const weeks = [];
  193. const start = weekBegin || 1;
  194. const end = weekEnd || start;
  195. for (let w = start; w <= end; w++) {
  196. if (lessType === '单周' && w % 2 === 0) continue;
  197. if (lessType === '双周' && w % 2 === 1) continue;
  198. weeks.push(w);
  199. }
  200. return weeks;
  201. }
  202. /**
  203. * 把 teachTask/list 返回的数组转换成 saveImportedCourses 所需格式
  204. * 注意:day 直接用 API 的 weekDay(1=周一 ... 7=周日)
  205. * 若发现课程排错星期,改成 task.weekDay - 1(0=周一)即可
  206. */
  207. function transformTeachTaskToCourses(taskList) {
  208. const courses = [];
  209. (taskList || []).forEach(task => {
  210. // 教师名拼接(多个教师用「、」连接)
  211. const teachers = (task.teacherList || [])
  212. .map(t => t.teacherName)
  213. .filter(name => name && String(name).trim().length > 0);
  214. const teacher = teachers.length > 0 ? teachers.join('、') : '未知教师';
  215. const course = {
  216. name: task.courName || '未知课程',
  217. teacher: teacher,
  218. position: task.roomId ? String(task.roomId) : '未知教室',
  219. day: task.weekDay, // API: 1=周一 ... 7=周日
  220. startSection: task.startSection,
  221. endSection: task.endSection,
  222. weeks: buildWeeksArray(task.weekBegin, task.weekEnd, task.lessType),
  223. isCustomTime: false
  224. };
  225. courses.push(course);
  226. });
  227. return courses;
  228. }
  229. // ========== 根据季节获取时间段(夏季/秋季) ==========
  230. /**
  231. * 根据季节返回对应的时间段数组
  232. * @param {string} season - "summer" 或 "autumn"
  233. * @returns {Array} 时间段对象数组
  234. */
  235. function getSeasonTimeSlots(season) {
  236. // 上午固定时间(夏秋通用)
  237. const morning_classes = [
  238. {"number": 1, "startTime": "08:10", "endTime": "08:55"},
  239. {"number": 2, "startTime": "09:00", "endTime": "09:45"},
  240. {"number": 3, "startTime": "10:05", "endTime": "10:50"},
  241. {"number": 4, "startTime": "10:55", "endTime": "11:40"}
  242. ];
  243. // 夏季下午/晚上时间
  244. const summer_afternoon_evening = [
  245. {"number": 5, "startTime": "14:30", "endTime": "15:15"},
  246. {"number": 6, "startTime": "15:20", "endTime": "16:05"},
  247. {"number": 7, "startTime": "16:25", "endTime": "17:10"},
  248. {"number": 8, "startTime": "17:15", "endTime": "18:00"},
  249. {"number": 9, "startTime": "18:45", "endTime": "19:30"},
  250. {"number": 10, "startTime": "19:35", "endTime": "20:20"},
  251. {"number": 11, "startTime": "20:25", "endTime": "21:10"}
  252. ];
  253. // 秋季下午/晚上时间
  254. const autumn_afternoon_evening = [
  255. {"number": 5, "startTime": "14:00", "endTime": "14:45"},
  256. {"number": 6, "startTime": "14:50", "endTime": "15:35"},
  257. {"number": 7, "startTime": "15:55", "endTime": "16:40"},
  258. {"number": 8, "startTime": "16:45", "endTime": "17:30"},
  259. {"number": 9, "startTime": "18:15", "endTime": "19:00"},
  260. {"number": 10, "startTime": "19:05", "endTime": "19:50"},
  261. {"number": 11, "startTime": "19:55", "endTime": "20:40"}
  262. ];
  263. if (season === 'autumn') {
  264. return morning_classes.concat(autumn_afternoon_evening);
  265. }
  266. // 默认夏季(兼容旧调用)
  267. return morning_classes.concat(summer_afternoon_evening);
  268. }
  269. // ==================== 弹窗和导入函数 ====================
  270. async function demoAlert() {
  271. try {
  272. const confirmed = await window.shiguangBridgePromise.showAlert(
  273. "📚 湖北汽车工业学院课表导入",
  274. "将通过教务系统 API 拉取课表数据并导入到 App\n\n" +
  275. "📌 请确认已登录教务系统\n" +
  276. "📌 流程:选择学期 → 选择季节 → 拉取课程",
  277. "开始导入",
  278. "取消"
  279. );
  280. return confirmed;
  281. } catch (error) {
  282. console.error("显示弹窗错误:", error);
  283. return false;
  284. }
  285. }
  286. async function demoPrompt(semesterInfo) {
  287. try {
  288. const defaultDate = (semesterInfo && semesterInfo.semesterStartDate) || '2026-09-07';
  289. const semesterStart = await window.shiguangBridgePromise.showPrompt(
  290. "📅 设置开学日期",
  291. "已从教务系统获取开学日期,如需修改请直接编辑",
  292. defaultDate,
  293. "validateDate"
  294. );
  295. return semesterStart || defaultDate;
  296. } catch (error) {
  297. console.error("日期输入错误:", error);
  298. return "2026-09-07";
  299. }
  300. }
  301. /**
  302. * 学期选择 - 用单选列表让用户选学期
  303. * 默认选中 currYearTerm === 1 的当前学期
  304. */
  305. async function pickSemester(semesterList) {
  306. if (!semesterList || semesterList.length === 0) return null;
  307. const names = semesterList.map(s => s.yearTermName || s.yearTermID);
  308. // 默认选中当前学期(currYearTerm === 1)
  309. let defaultIdx = semesterList.findIndex(s => s.currYearTerm === 1);
  310. if (defaultIdx < 0) defaultIdx = 0;
  311. try {
  312. console.log("即将显示学期选择弹窗...");
  313. const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  314. "选择要导入的学期",
  315. JSON.stringify(names),
  316. defaultIdx
  317. );
  318. if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < semesterList.length) {
  319. const picked = semesterList[selectedIndex];
  320. console.log("用户选择了: " + picked.yearTermName + " (yearTermID=" + picked.yearTermID + ")");
  321. window.shiguangBridge.showToast("已选 " + picked.yearTermName);
  322. return picked;
  323. }
  324. console.log("用户取消了学期选择。");
  325. window.shiguangBridge.showToast("学期选择:用户取消");
  326. return null;
  327. } catch (error) {
  328. console.error("显示学期选择弹窗出错:", error);
  329. window.shiguangBridge.showToast("学期选择出错:" + error.message);
  330. return null;
  331. }
  332. }
  333. /**
  334. * 导入时间段 - 使用单选列表弹窗让用户选择夏季或秋季
  335. * 参考 demoSingleSelection 的调用模式
  336. */
  337. async function importPresetTimeSlots() {
  338. const seasons = ["夏季", "秋季"];
  339. try {
  340. console.log("即将显示季节选择单选列表弹窗...");
  341. const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  342. "选择作息时间",
  343. JSON.stringify(seasons),
  344. 0
  345. );
  346. if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < seasons.length) {
  347. console.log("用户选择了: " + seasons[selectedIndex] + " (索引: " + selectedIndex + ")");
  348. window.shiguangBridge.showToast("你选择了 " + seasons[selectedIndex]);
  349. // 索引 0 -> 夏季,索引 1 -> 秋季
  350. const season = selectedIndex === 0 ? 'summer' : 'autumn';
  351. // 获取对应季节的时间段
  352. const presetTimeSlots = getSeasonTimeSlots(season);
  353. console.log(`选择的季节: ${season},时间段:`, presetTimeSlots);
  354. // 导入时间段
  355. const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  356. if (result === true) {
  357. console.log("预设时间段导入成功!");
  358. window.shiguangBridge.showToast(`✅ ${seasons[selectedIndex]}时间段导入成功!`);
  359. return true; // 成功时返回 true
  360. } else {
  361. console.log("预设时间段导入未成功,结果:" + result);
  362. window.shiguangBridge.showToast("❌ 时间段导入失败,请查看日志。");
  363. return false;
  364. }
  365. } else {
  366. console.log("用户取消了选择。");
  367. window.shiguangBridge.showToast("季节选择:用户取消了选择!");
  368. return false; // 用户取消时返回 false
  369. }
  370. } catch (error) {
  371. console.error("显示季节选择单选列表弹窗时发生错误:", error);
  372. window.shiguangBridge.showToast("季节选择:显示列表出错!" + error.message);
  373. return false; // 出现错误时也返回 false
  374. }
  375. }
  376. /**
  377. * 从 API 拉取课程并导入(按选中的 yearTermId 过滤)
  378. */
  379. async function importSchedule(semesterInfo) {
  380. const result = { success: false, courseCount: 0, errorMsg: null };
  381. try {
  382. window.shiguangBridge.showToast("正在从教务系统拉取课表...");
  383. const yearTermId = semesterInfo && semesterInfo.yearTermId;
  384. const tasks = await fetchTeachTaskList(yearTermId);
  385. console.log(`拉取到 ${tasks.length} 条教学任务`);
  386. const courses = transformTeachTaskToCourses(tasks);
  387. result.courseCount = courses.length;
  388. if (courses.length === 0) {
  389. result.errorMsg = "教务系统未返回任何课程数据";
  390. await window.shiguangBridgePromise.showAlert(
  391. "⚠️ 拉取失败",
  392. result.errorMsg + ",请确认已登录且所选学期有课程",
  393. "知道了"
  394. );
  395. return result;
  396. }
  397. // 预览
  398. const preview = await window.shiguangBridgePromise.showAlert(
  399. "📊 数据预览",
  400. `共拉取到 ${courses.length} 条课程记录\n\n` +
  401. `示例:\n${courses.slice(0, 5).map(c =>
  402. `• 周${c.day} ${c.name} - 第${c.startSection}-${c.endSection}节`
  403. ).join('\n')}`,
  404. "确认导入",
  405. "取消"
  406. );
  407. if (!preview) {
  408. result.errorMsg = "用户在预览时取消";
  409. return result;
  410. }
  411. // 导入课程
  412. window.shiguangBridge.showToast("正在导入课程...");
  413. const saveResult = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  414. if (saveResult !== true) {
  415. result.errorMsg = "saveImportedCourses 返回非 true";
  416. return result;
  417. }
  418. // 设置开学日期(默认值来自学期 API 的 beginDate)
  419. const semesterDate = await demoPrompt(semesterInfo);
  420. const totalWeeks = (semesterInfo && semesterInfo.semesterTotalWeeks) || 25;
  421. // 保存课表配置
  422. const configResult = await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify({
  423. semesterStartDate: semesterDate,
  424. semesterTotalWeeks: totalWeeks,
  425. defaultClassDuration: 45,
  426. defaultBreakDuration: 10,
  427. firstDayOfWeek: 1
  428. }));
  429. if (configResult === true) {
  430. result.success = true;
  431. return result;
  432. }
  433. result.errorMsg = "saveCourseConfig 返回非 true";
  434. return result;
  435. } catch (error) {
  436. console.error("导入错误:", error);
  437. result.errorMsg = error.message;
  438. return result;
  439. }
  440. }
  441. /**
  442. * 显示最终结果汇总弹窗
  443. */
  444. async function showFinalResult(results) {
  445. const success = results.schedule && results.schedule.success;
  446. const title = success ? '🎉 导入成功' : '❌ 导入失败';
  447. let msg = '';
  448. if (results.semesterName) msg += '📚 学期: ' + results.semesterName + '\n';
  449. if (results.schedule && results.schedule.courseCount > 0) {
  450. msg += '📖 课程: ' + results.schedule.courseCount + ' 条\n';
  451. }
  452. msg += '\n📋 步骤状态:\n';
  453. msg += (results.semesterList ? '✅' : '❌') + ' 获取学期列表\n';
  454. msg += (results.season ? '✅' : '❌') + ' 导入时间段';
  455. if (results.seasonName) msg += ' (' + results.seasonName + ')';
  456. msg += '\n';
  457. msg += (results.schedule ? (results.schedule.success ? '✅' : '❌') : '⏭️') + ' 导入课表';
  458. if (results.schedule && results.schedule.courseCount > 0) msg += ' (' + results.schedule.courseCount + '条)';
  459. msg += '\n';
  460. if (results.errorMsg) {
  461. msg += '\n❗ 错误: ' + results.errorMsg;
  462. }
  463. try {
  464. await window.shiguangBridgePromise.showAlert(title, msg, '知道了');
  465. } catch (e) {
  466. console.error('显示最终结果弹窗失败:', e);
  467. window.shiguangBridge.showToast(title);
  468. }
  469. }
  470. async function runAllDemosSequentially() {
  471. window.shiguangBridge.showToast("🚀 课表导入助手启动...");
  472. const results = {
  473. semesterList: false,
  474. semesterName: '',
  475. season: false,
  476. seasonName: '',
  477. schedule: null,
  478. errorMsg: null
  479. };
  480. try {
  481. // 1. 确认开始
  482. const start = await demoAlert();
  483. if (!start) {
  484. results.errorMsg = '用户取消(开始按钮)';
  485. await showFinalResult(results);
  486. return;
  487. }
  488. // 2. 拉学期列表
  489. let semesterList = [];
  490. try {
  491. window.shiguangBridge.showToast("正在获取学期列表...");
  492. semesterList = await fetchSemesterList();
  493. console.log(`拉取到 ${semesterList.length} 个学期`);
  494. results.semesterList = true;
  495. } catch (e) {
  496. console.error("获取学期列表失败:", e);
  497. results.errorMsg = e.message;
  498. await showFinalResult(results);
  499. return;
  500. }
  501. // 3. 让用户选择学期
  502. const pickedSemester = await pickSemester(semesterList);
  503. if (!pickedSemester) {
  504. results.errorMsg = '用户取消了学期选择';
  505. await showFinalResult(results);
  506. return;
  507. }
  508. results.semesterName = pickedSemester.yearTermName;
  509. const semesterInfo = buildSemesterInfoFromSemester(pickedSemester);
  510. // 4. 导入时间段(季节选择:夏季/秋季)
  511. results.season = await importPresetTimeSlots();
  512. // 季节名映射(与 importPresetTimeSlots 内部一致)
  513. // 这里不直接拿名字,留空也行;如果想显示需要改 importPresetTimeSlots 返回值
  514. // 5. 导入课表(用选中的 yearTermId 拉 teachTask)
  515. results.schedule = await importSchedule(semesterInfo);
  516. } catch (e) {
  517. console.error("运行错误:", e);
  518. results.errorMsg = e.message;
  519. }
  520. await showFinalResult(results);
  521. window.shiguangBridge.notifyTaskCompletion();
  522. }
  523. // 导出函数
  524. window.validateDate = validateDate;
  525. window.getAuthToken = getAuthToken;
  526. window.getStudentId = getStudentId;
  527. window.decodeJwtPayload = decodeJwtPayload;
  528. window.fetchSemesterList = fetchSemesterList;
  529. window.fetchTeachTaskList = fetchTeachTaskList;
  530. window.pickSemester = pickSemester;
  531. window.transformTeachTaskToCourses = transformTeachTaskToCourses;
  532. window.getSeasonTimeSlots = getSeasonTimeSlots;
  533. window.importPresetTimeSlots = importPresetTimeSlots;
  534. window.importSchedule = importSchedule;
  535. // 启动
  536. runAllDemosSequentially();