sdlivc.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // 山东轻工职业学院(sdlivc.cn) 拾光课程表适配脚本
  2. // 数据来源:教务系统 /jedu/edu/core/eduScheduleInfo/getScheduleNew.do
  3. (function () {
  4. const ROOT_PATH = window.__rootPath || '/jedu';
  5. const WEEK_MAP = {
  6. mon: 1,
  7. tue: 2,
  8. wed: 3,
  9. thu: 4,
  10. fri: 5,
  11. sat: 6,
  12. sun: 7
  13. };
  14. function showToast(message) {
  15. if (window.AndroidBridge && typeof window.AndroidBridge.showToast === 'function') {
  16. window.AndroidBridge.showToast(message);
  17. } else {
  18. console.log('[Toast]', message);
  19. }
  20. }
  21. function getStudentId() {
  22. if (typeof window.stuId === 'string' && window.stuId.trim()) {
  23. return window.stuId.trim();
  24. }
  25. const html = document.body ? document.body.innerHTML : '';
  26. const match = html.match(/var\s+stuId\s*=\s*["']([^"']+)["']/);
  27. return match ? match[1] : '';
  28. }
  29. function getSelectedSemesterId() {
  30. if (typeof mini !== 'undefined' && typeof mini.get === 'function') {
  31. const picker = mini.get('semId');
  32. if (picker && typeof picker.getValue === 'function') {
  33. return picker.getValue() || '';
  34. }
  35. }
  36. const input = document.querySelector('input[name="semId"], #semId');
  37. return input ? input.value || '' : '';
  38. }
  39. function toDayNumber(week) {
  40. return WEEK_MAP[String(week || '').toLowerCase()] || null;
  41. }
  42. function expandWeekRange(start, end, parity) {
  43. const weeks = [];
  44. const from = Math.min(start, end);
  45. const to = Math.max(start, end);
  46. for (let week = from; week <= to; week += 1) {
  47. if (parity === 'odd' && week % 2 === 0) continue;
  48. if (parity === 'even' && week % 2 !== 0) continue;
  49. weeks.push(week);
  50. }
  51. return weeks;
  52. }
  53. function parseWeeks(weekList) {
  54. if (!weekList) return [];
  55. const weeks = new Set();
  56. const normalized = String(weekList)
  57. .replace(/\s+/g, '')
  58. .replace(/,/g, ',')
  59. .replace(/(/g, '(')
  60. .replace(/)/g, ')')
  61. .replace(/第/g, '')
  62. .replace(/周/g, '');
  63. for (const rawPart of normalized.split(',')) {
  64. if (!rawPart) continue;
  65. const parity = rawPart.includes('单') ? 'odd' : rawPart.includes('双') ? 'even' : null;
  66. const numberPart = rawPart.replace(/\([^)]*\)/g, '');
  67. const rangeMatch = numberPart.match(/^(\d+)-(\d+)$/);
  68. const singleMatch = numberPart.match(/^(\d+)$/);
  69. if (rangeMatch) {
  70. expandWeekRange(Number(rangeMatch[1]), Number(rangeMatch[2]), parity)
  71. .forEach(week => weeks.add(week));
  72. } else if (singleMatch) {
  73. const week = Number(singleMatch[1]);
  74. if (parity === 'odd' && week % 2 === 0) continue;
  75. if (parity === 'even' && week % 2 !== 0) continue;
  76. weeks.add(week);
  77. }
  78. }
  79. return Array.from(weeks).sort((a, b) => a - b);
  80. }
  81. function cleanPlaceName(item) {
  82. const place = item && item.eduPlace ? item.eduPlace : {};
  83. return place.placeName || place.nameIncludeNum || '';
  84. }
  85. function convertScheduleItem(item) {
  86. if (!item || item.stopCourse !== 'NO') return null;
  87. const lesson = item.eduLesson || {};
  88. const day = toDayNumber(item.week);
  89. const weeks = parseWeeks(item.weekList);
  90. const startSection = Number(lesson.startLesson);
  91. const endSection = Number(lesson.endLesson);
  92. if (!item.courseName || !day || !startSection || !endSection || weeks.length === 0) {
  93. return null;
  94. }
  95. return {
  96. name: item.courseName,
  97. teacher: item.teacherName || '',
  98. position: cleanPlaceName(item),
  99. day,
  100. startSection,
  101. endSection,
  102. weeks,
  103. isCustomTime: false
  104. };
  105. }
  106. function mergeCourses(courses) {
  107. const courseMap = new Map();
  108. courses.forEach(course => {
  109. const key = [
  110. course.name,
  111. course.teacher,
  112. course.position,
  113. course.day,
  114. course.startSection,
  115. course.endSection
  116. ].join('|');
  117. const existing = courseMap.get(key);
  118. if (existing) {
  119. existing.weeks = Array.from(new Set(existing.weeks.concat(course.weeks))).sort((a, b) => a - b);
  120. } else {
  121. courseMap.set(key, Object.assign({}, course, {
  122. weeks: Array.from(new Set(course.weeks)).sort((a, b) => a - b)
  123. }));
  124. }
  125. });
  126. return Array.from(courseMap.values());
  127. }
  128. function getMaxWeek(courses) {
  129. const weeks = courses.flatMap(course => Array.isArray(course.weeks) ? course.weeks : []);
  130. return weeks.length > 0 ? Math.max.apply(null, weeks) : 20;
  131. }
  132. async function fetchSchedule() {
  133. const stuId = getStudentId();
  134. if (!stuId) {
  135. throw new Error('未找到学生 ID,请先通过学生信息系统进入“学期课表”页面。');
  136. }
  137. const params = new URLSearchParams({
  138. semId: getSelectedSemesterId(),
  139. stuId,
  140. checkType: 'student'
  141. });
  142. const response = await fetch(ROOT_PATH + '/edu/core/eduScheduleInfo/getScheduleNew.do', {
  143. method: 'POST',
  144. credentials: 'same-origin',
  145. headers: {
  146. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  147. 'X-Requested-With': 'XMLHttpRequest'
  148. },
  149. body: params.toString()
  150. });
  151. if (!response.ok) {
  152. throw new Error('课表接口请求失败:HTTP ' + response.status);
  153. }
  154. const json = await response.json();
  155. if (!json || json.success !== true) {
  156. throw new Error((json && json.message) || '课表接口返回失败。');
  157. }
  158. return json.data && Array.isArray(json.data.schedule) ? json.data.schedule : [];
  159. }
  160. async function saveImportedData(courses) {
  161. const courseResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
  162. if (courseResult !== true) {
  163. throw new Error('课程保存失败:' + courseResult);
  164. }
  165. if (typeof window.AndroidBridgePromise.saveCourseConfig === 'function') {
  166. const configResult = await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
  167. semesterTotalWeeks: getMaxWeek(courses),
  168. defaultClassDuration: 45,
  169. defaultBreakDuration: 10,
  170. firstDayOfWeek: 1
  171. }));
  172. if (configResult !== true) {
  173. throw new Error('课表配置保存失败:' + configResult);
  174. }
  175. }
  176. }
  177. async function promptUserToStart() {
  178. if (!window.AndroidBridgePromise || typeof window.AndroidBridgePromise.showAlert !== 'function') {
  179. return true;
  180. }
  181. return await window.AndroidBridgePromise.showAlert(
  182. '山东轻工职业学院课表导入',
  183. '请确认已登录并进入“学期课表”页面。脚本将读取当前学期课表并导入拾光课程表。',
  184. '开始导入'
  185. );
  186. }
  187. async function runImportFlow() {
  188. try {
  189. const confirmed = await promptUserToStart();
  190. if (!confirmed) return;
  191. showToast('正在读取学期课表...');
  192. const rawSchedule = await fetchSchedule();
  193. const courses = mergeCourses(rawSchedule.map(convertScheduleItem).filter(Boolean));
  194. if (courses.length === 0) {
  195. showToast('未解析到可导入课程');
  196. return;
  197. }
  198. await saveImportedData(courses);
  199. showToast('成功导入 ' + courses.length + ' 条课程');
  200. window.AndroidBridge.notifyTaskCompletion();
  201. } catch (error) {
  202. console.error('山东轻工职业学院课表导入失败', error);
  203. showToast('课表导入失败:' + error.message);
  204. }
  205. }
  206. window.__sdlivcImporter = {
  207. parseWeeks,
  208. toDayNumber,
  209. convertScheduleItem,
  210. mergeCourses,
  211. getMaxWeek,
  212. saveImportedData
  213. };
  214. if (window.__SDLIVC_AUTO_RUN__ !== false) {
  215. runImportFlow();
  216. }
  217. }());