Просмотр исходного кода

Merge pull request #324 from XingHeYuZhuan/pending

Pending
星河欲转 1 месяц назад
Родитель
Сommit
88dd93e5a0

+ 5 - 0
index/root_index.yaml

@@ -148,6 +148,11 @@ schools:
     initial: "S"
     resource_folder: "SDDFVC"
 
+  - id: "SDLIVC"
+    name: "山东轻工职业学院"
+    initial: "S"
+    resource_folder: "SDLIVC"
+
   - id: "CQCIVC"
     name: "重庆化工职业学院"
     initial: "C"

+ 19 - 40
resources/CFEC/CFEC.js

@@ -1,7 +1,22 @@
 const BASE = `${window.location.origin}/jwglxt`;
 const INDEX_PATH = '/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default';
 const COURSE_API_PATH = '/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151';
-const TIME_API_PATH = '/kbcx/xskbcx_cxRjc.html?gnmkdm=N2151';
+
+const TIME_SLOTS = [
+  { number: 1, startTime: '08:10', endTime: '08:50' },
+  { number: 2, startTime: '09:00', endTime: '09:40' },
+  { number: 3, startTime: '09:50', endTime: '10:30' },
+  { number: 4, startTime: '10:40', endTime: '11:20' },
+  { number: 5, startTime: '11:30', endTime: '12:10' },
+  { number: 6, startTime: '14:10', endTime: '14:50' },
+  { number: 7, startTime: '15:00', endTime: '15:40' },
+  { number: 8, startTime: '15:50', endTime: '16:30' },
+  { number: 9, startTime: '16:40', endTime: '17:20' },
+  { number: 10, startTime: '18:30', endTime: '19:10' },
+  { number: 11, startTime: '19:20', endTime: '20:00' },
+  { number: 12, startTime: '20:10', endTime: '20:50' },
+  { number: 13, startTime: '21:00', endTime: '21:40' },
+];
 
 async function req(url, method = 'GET', body) {
   const res = await fetch(url, {
@@ -151,66 +166,30 @@ function parseCourses(data) {
   return { courses: [...deduped.values()], xqhId };
 }
 
-function parseTimeSlots(data) {
-  if (!Array.isArray(data) || !data.length) throw new Error('未获取到节次时间数据');
-  return data.map((item) => ({
-    number: Number(item.jcmc),
-    startTime: String(item.qssj || '').trim(),
-    endTime: String(item.jssj || '').trim()
-  })).filter(item => item.number > 0 && item.startTime && item.endTime);
-}
-
 async function fetchCourses(xnm, xqm) {
   const body = `xnm=${encodeURIComponent(xnm)}&xqm=${encodeURIComponent(xqm)}&kzlx=ck&xsdm=&kclbdm=&kclxdm=`;
   const text = await req(`${BASE}${COURSE_API_PATH}`, 'POST', body);
   return JSON.parse(text);
 }
 
-async function fetchTimeSlots(xnm, xqm, xqhId) {
-  const body = `xnm=${encodeURIComponent(xnm)}&xqm=${encodeURIComponent(xqm)}&xqh_id=${encodeURIComponent(xqhId || '1')}`;
-  const text = await req(`${BASE}${TIME_API_PATH}`, 'POST', body);
-  return parseTimeSlots(JSON.parse(text));
-}
-
-function validateSemesterStartDateInput(input) {
-  const value = String(input || '').trim();
-  if (!value) return false;
-  return /^\d{4}-\d{2}-\d{2}$/.test(value) ? false : '请输入 YYYY-MM-DD,例如 2026-02-24';
-}
-
-async function selectSemesterStartDate(xnm, xqm) {
-  const defaultDate = xqm === '3' ? `${xnm}-09-01` : `${Number(xnm) + 1}-03-01`;
-  const picked = await window.AndroidBridgePromise.showPrompt(
-    '选择开学日期',
-    '请输入开学日期(YYYY-MM-DD)',
-    defaultDate,
-    'validateSemesterStartDateInput'
-  );
-  if (picked === null) return null;
-  const value = String(picked).trim();
-  return value || null;
-}
-
 async function run() {
   try {
     const { xnm, xqm } = await resolveTerm();
     AndroidBridge.showToast('正在解析课表数据...');
 
     const rawData = await fetchCourses(xnm, xqm);
-    const { courses, xqhId } = parseCourses(rawData);
+    const { courses } = parseCourses(rawData);
     if (!courses.length) throw new Error('未获取到课表数据');
 
-    const semesterStartDate = await selectSemesterStartDate(xnm, xqm);
-    const timeSlots = await fetchTimeSlots(xnm, xqm, xqhId);
     const allWeeks = courses.flatMap(course => course.weeks);
     const semesterTotalWeeks = allWeeks.length ? Math.max(...allWeeks) : 20;
 
     await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
       semesterTotalWeeks,
-      semesterStartDate,
+      semesterStartDate: null,
       firstDayOfWeek: 1
     }));
-    await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+    await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(TIME_SLOTS));
     await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
 
     AndroidBridge.showToast(`导入成功:${courses.length} 门`);

+ 14 - 5
resources/ECJTU/ecjtu_01.js

@@ -1,8 +1,3 @@
-// 文件: ECJTU_01.js
-// 功能:从华东交通大学系统获取课程表,解析后导入到拾光课程表
-// 适配:华东交通大学教务系统
-// 维护者:glxgo
-
 const BASE = window.location.origin;
 const SCHEDULE_PATHS = [
   '/Schedule/Schedule_getUserSchedume.action?item=0207',
@@ -10,6 +5,19 @@ const SCHEDULE_PATHS = [
   '/Schedule/Schedule_getUserSchedume.action'
 ];
 
+const TIME_SLOTS = [
+  { number: 1, startTime: '08:00', endTime: '08:45' },
+  { number: 2, startTime: '08:55', endTime: '09:40' },
+  { number: 3, startTime: '10:05', endTime: '10:50' },
+  { number: 4, startTime: '10:55', endTime: '11:40' },
+  { number: 5, startTime: '14:30', endTime: '15:15' },
+  { number: 6, startTime: '15:25', endTime: '16:10' },
+  { number: 7, startTime: '16:40', endTime: '17:25' },
+  { number: 8, startTime: '17:35', endTime: '18:20' },
+  { number: 9, startTime: '19:00', endTime: '19:45' },
+  { number: 10, startTime: '19:55', endTime: '20:40' },
+];
+
 function cleanText(value) {
   return String(value || '')
     .replace(/[​-‍]/g, '')
@@ -252,6 +260,7 @@ async function runImportFlow() {
       semesterStartDate: null,
       firstDayOfWeek: 1
     }));
+    await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(TIME_SLOTS));
     await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
 
     AndroidBridge.showToast(`导入成功:共 ${courses.length} 门课程`);

+ 9 - 0
resources/SDLIVC/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/SDLIVC/adapters.yaml
+adapters:
+  - adapter_id: "SDLIVC"
+    adapter_name: "山东轻工职业学院教务"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "sdlivc.js"
+    import_url: "https://crp.sdlivc.cn/xsgl/xs/login/login.aspx"
+    maintainer: "赛博丁真"
+    description: "山东轻工职业学院教务适配"

+ 259 - 0
resources/SDLIVC/sdlivc.js

@@ -0,0 +1,259 @@
+// 山东轻工职业学院(sdlivc.cn) 拾光课程表适配脚本
+// 数据来源:教务系统 /jedu/edu/core/eduScheduleInfo/getScheduleNew.do
+
+(function () {
+    const ROOT_PATH = window.__rootPath || '/jedu';
+    const WEEK_MAP = {
+        mon: 1,
+        tue: 2,
+        wed: 3,
+        thu: 4,
+        fri: 5,
+        sat: 6,
+        sun: 7
+    };
+
+    function showToast(message) {
+        if (window.AndroidBridge && typeof window.AndroidBridge.showToast === 'function') {
+            window.AndroidBridge.showToast(message);
+        } else {
+            console.log('[Toast]', message);
+        }
+    }
+
+    function getStudentId() {
+        if (typeof window.stuId === 'string' && window.stuId.trim()) {
+            return window.stuId.trim();
+        }
+
+        const html = document.body ? document.body.innerHTML : '';
+        const match = html.match(/var\s+stuId\s*=\s*["']([^"']+)["']/);
+        return match ? match[1] : '';
+    }
+
+    function getSelectedSemesterId() {
+        if (typeof mini !== 'undefined' && typeof mini.get === 'function') {
+            const picker = mini.get('semId');
+            if (picker && typeof picker.getValue === 'function') {
+                return picker.getValue() || '';
+            }
+        }
+
+        const input = document.querySelector('input[name="semId"], #semId');
+        return input ? input.value || '' : '';
+    }
+
+    function toDayNumber(week) {
+        return WEEK_MAP[String(week || '').toLowerCase()] || null;
+    }
+
+    function expandWeekRange(start, end, parity) {
+        const weeks = [];
+        const from = Math.min(start, end);
+        const to = Math.max(start, end);
+
+        for (let week = from; week <= to; week += 1) {
+            if (parity === 'odd' && week % 2 === 0) continue;
+            if (parity === 'even' && week % 2 !== 0) continue;
+            weeks.push(week);
+        }
+
+        return weeks;
+    }
+
+    function parseWeeks(weekList) {
+        if (!weekList) return [];
+
+        const weeks = new Set();
+        const normalized = String(weekList)
+            .replace(/\s+/g, '')
+            .replace(/,/g, ',')
+            .replace(/(/g, '(')
+            .replace(/)/g, ')')
+            .replace(/第/g, '')
+            .replace(/周/g, '');
+
+        for (const rawPart of normalized.split(',')) {
+            if (!rawPart) continue;
+
+            const parity = rawPart.includes('单') ? 'odd' : rawPart.includes('双') ? 'even' : null;
+            const numberPart = rawPart.replace(/\([^)]*\)/g, '');
+            const rangeMatch = numberPart.match(/^(\d+)-(\d+)$/);
+            const singleMatch = numberPart.match(/^(\d+)$/);
+
+            if (rangeMatch) {
+                expandWeekRange(Number(rangeMatch[1]), Number(rangeMatch[2]), parity)
+                    .forEach(week => weeks.add(week));
+            } else if (singleMatch) {
+                const week = Number(singleMatch[1]);
+                if (parity === 'odd' && week % 2 === 0) continue;
+                if (parity === 'even' && week % 2 !== 0) continue;
+                weeks.add(week);
+            }
+        }
+
+        return Array.from(weeks).sort((a, b) => a - b);
+    }
+
+    function cleanPlaceName(item) {
+        const place = item && item.eduPlace ? item.eduPlace : {};
+        return place.placeName || place.nameIncludeNum || '';
+    }
+
+    function convertScheduleItem(item) {
+        if (!item || item.stopCourse !== 'NO') return null;
+
+        const lesson = item.eduLesson || {};
+        const day = toDayNumber(item.week);
+        const weeks = parseWeeks(item.weekList);
+        const startSection = Number(lesson.startLesson);
+        const endSection = Number(lesson.endLesson);
+
+        if (!item.courseName || !day || !startSection || !endSection || weeks.length === 0) {
+            return null;
+        }
+
+        return {
+            name: item.courseName,
+            teacher: item.teacherName || '',
+            position: cleanPlaceName(item),
+            day,
+            startSection,
+            endSection,
+            weeks,
+            isCustomTime: false
+        };
+    }
+
+    function mergeCourses(courses) {
+        const courseMap = new Map();
+
+        courses.forEach(course => {
+            const key = [
+                course.name,
+                course.teacher,
+                course.position,
+                course.day,
+                course.startSection,
+                course.endSection
+            ].join('|');
+
+            const existing = courseMap.get(key);
+            if (existing) {
+                existing.weeks = Array.from(new Set(existing.weeks.concat(course.weeks))).sort((a, b) => a - b);
+            } else {
+                courseMap.set(key, Object.assign({}, course, {
+                    weeks: Array.from(new Set(course.weeks)).sort((a, b) => a - b)
+                }));
+            }
+        });
+
+        return Array.from(courseMap.values());
+    }
+
+    function getMaxWeek(courses) {
+        const weeks = courses.flatMap(course => Array.isArray(course.weeks) ? course.weeks : []);
+        return weeks.length > 0 ? Math.max.apply(null, weeks) : 20;
+    }
+
+    async function fetchSchedule() {
+        const stuId = getStudentId();
+        if (!stuId) {
+            throw new Error('未找到学生 ID,请先通过学生信息系统进入“学期课表”页面。');
+        }
+
+        const params = new URLSearchParams({
+            semId: getSelectedSemesterId(),
+            stuId,
+            checkType: 'student'
+        });
+
+        const response = await fetch(ROOT_PATH + '/edu/core/eduScheduleInfo/getScheduleNew.do', {
+            method: 'POST',
+            credentials: 'same-origin',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+                'X-Requested-With': 'XMLHttpRequest'
+            },
+            body: params.toString()
+        });
+
+        if (!response.ok) {
+            throw new Error('课表接口请求失败:HTTP ' + response.status);
+        }
+
+        const json = await response.json();
+        if (!json || json.success !== true) {
+            throw new Error((json && json.message) || '课表接口返回失败。');
+        }
+
+        return json.data && Array.isArray(json.data.schedule) ? json.data.schedule : [];
+    }
+
+    async function saveImportedData(courses) {
+        const courseResult = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
+        if (courseResult !== true) {
+            throw new Error('课程保存失败:' + courseResult);
+        }
+
+        if (typeof window.AndroidBridgePromise.saveCourseConfig === 'function') {
+            const configResult = await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
+                semesterTotalWeeks: getMaxWeek(courses),
+                defaultClassDuration: 45,
+                defaultBreakDuration: 10,
+                firstDayOfWeek: 1
+            }));
+            if (configResult !== true) {
+                throw new Error('课表配置保存失败:' + configResult);
+            }
+        }
+    }
+
+    async function promptUserToStart() {
+        if (!window.AndroidBridgePromise || typeof window.AndroidBridgePromise.showAlert !== 'function') {
+            return true;
+        }
+
+        return await window.AndroidBridgePromise.showAlert(
+            '山东轻工职业学院课表导入',
+            '请确认已登录并进入“学期课表”页面。脚本将读取当前学期课表并导入拾光课程表。',
+            '开始导入'
+        );
+    }
+
+    async function runImportFlow() {
+        try {
+            const confirmed = await promptUserToStart();
+            if (!confirmed) return;
+
+            showToast('正在读取学期课表...');
+            const rawSchedule = await fetchSchedule();
+            const courses = mergeCourses(rawSchedule.map(convertScheduleItem).filter(Boolean));
+
+            if (courses.length === 0) {
+                showToast('未解析到可导入课程');
+                return;
+            }
+
+            await saveImportedData(courses);
+            showToast('成功导入 ' + courses.length + ' 条课程');
+            window.AndroidBridge.notifyTaskCompletion();
+        } catch (error) {
+            console.error('山东轻工职业学院课表导入失败', error);
+            showToast('课表导入失败:' + error.message);
+        }
+    }
+
+    window.__sdlivcImporter = {
+        parseWeeks,
+        toDayNumber,
+        convertScheduleItem,
+        mergeCourses,
+        getMaxWeek,
+        saveImportedData
+    };
+
+    if (window.__SDLIVC_AUTO_RUN__ !== false) {
+        runImportFlow();
+    }
+}());

+ 1 - 6
resources/WENHUA/wenhua_01.js

@@ -1,8 +1,3 @@
-// 文件: WENHUA_01.js
-// 功能:从文华学院正方教务系统获取课程表,解析后导入到拾光课程表
-// 适配:文华学院正方教务系统
-// 维护者:glxgo
-
 const BASE = `${window.location.origin}/jwglxt`;
 const INDEX_PATH = '/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default';
 const COURSE_API_PATH = '/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151';
@@ -159,7 +154,7 @@ function parseCourses(data) {
 function parseTimeSlots(data) {
   if (!Array.isArray(data) || !data.length) throw new Error('未获取到节次时间数据');
   return data.map((item) => ({
-    number: Number(item.jcmc),
+    number: Number(item.jcdm || item.jcmc),
     startTime: String(item.qssj || '').trim(),
     endTime: String(item.jssj || '').trim()
   })).filter(item => item.number > 0 && item.startTime && item.endTime);

+ 15 - 26
resources/ZJUT/zjut_01.js

@@ -1,13 +1,20 @@
-// 文件: ZJUT_01.js
-// 功能:从浙江工业大学正方教务系统获取课程表,解析后导入到拾光课程表
-// 适配:浙江工业大学正方教务系统
-// 维护者:glxgo
-
 const BASE = `${window.location.origin}/jwglxt`;
 const GNMKDM = 'N253508';
 const INDEX_PATH = `/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=${GNMKDM}&layout=default`;
 const COURSE_API_PATH = `/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=${GNMKDM}`;
-const TIME_API_PATH = `/kbcx/xskbcx_cxRjc.html?gnmkdm=${GNMKDM}`;
+
+const TIME_SLOTS = [
+  { number: 1, startTime: '08:00', endTime: '08:45' },
+  { number: 2, startTime: '08:55', endTime: '09:40' },
+  { number: 3, startTime: '09:55', endTime: '10:40' },
+  { number: 4, startTime: '10:50', endTime: '11:35' },
+  { number: 5, startTime: '11:45', endTime: '12:30' },
+  { number: 6, startTime: '13:30', endTime: '14:15' },
+  { number: 7, startTime: '14:25', endTime: '15:10' },
+  { number: 8, startTime: '15:25', endTime: '16:10' },
+  { number: 9, startTime: '16:20', endTime: '17:05' },
+  { number: 10, startTime: '18:30', endTime: '21:30' },
+];
 
 async function req(url, method = 'GET', body) {
   const res = await fetch(url, {
@@ -157,36 +164,20 @@ function parseCourses(data) {
   return { courses: [...deduped.values()], xqhId };
 }
 
-function parseTimeSlots(data) {
-  if (!Array.isArray(data) || !data.length) throw new Error('未获取到节次时间数据');
-  return data.map((item) => ({
-    number: Number(item.jcmc),
-    startTime: String(item.qssj || '').trim(),
-    endTime: String(item.jssj || '').trim()
-  })).filter(item => item.number > 0 && item.startTime && item.endTime);
-}
-
 async function fetchCourses(xnm, xqm) {
   const body = `xnm=${encodeURIComponent(xnm)}&xqm=${encodeURIComponent(xqm)}&kzlx=ck&xsdm=&kclbdm=&kclxdm=`;
   const text = await req(`${BASE}${COURSE_API_PATH}`, 'POST', body);
   return JSON.parse(text);
 }
 
-async function fetchTimeSlots(xnm, xqm) {
-  const body = `xnm=${encodeURIComponent(xnm)}&xqm=${encodeURIComponent(xqm)}`;
-  const text = await req(`${BASE}${TIME_API_PATH}`, 'POST', body);
-  return parseTimeSlots(JSON.parse(text));
-}
-
 async function run() {
   try {
     const { xnm, xqm } = await resolveTerm();
     AndroidBridge.showToast('正在解析课表数据...');
 
     const rawData = await fetchCourses(xnm, xqm);
-    const { courses, xqhId } = parseCourses(rawData);
+    const { courses } = parseCourses(rawData);
     if (!courses.length) throw new Error('未获取到课表数据');
-    const timeSlots = await fetchTimeSlots(xnm, xqm).catch(() => null);
 
     const allWeeks = courses.flatMap(course => course.weeks);
     const semesterTotalWeeks = allWeeks.length ? Math.max(...allWeeks) : 20;
@@ -196,9 +187,7 @@ async function run() {
       semesterStartDate: null,
       firstDayOfWeek: 1
     }));
-    if (timeSlots && timeSlots.length) {
-      await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
-    }
+    await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(TIME_SLOTS));
     await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(courses));
 
     AndroidBridge.showToast(`导入成功:${courses.length} 门`);