| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- // 武汉工程大学强智教务。接口与结构依据本校课表响应验证。
- // 开发参考:拾光官方强智适配案例(YSSDUFE,星河欲转)。
- (async function () {
- const lock = '__witImportRunning';
- if (window[lock]) return;
- window[lock] = true;
- const bridge = window.shiguangBridgePromise;
- const toast = message => window.shiguangBridge.showToast(message);
- const endpoint = '/jsxsd/xskb/xskb_list.do';
- async function semesterInfo(semester) {
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), 20000);
- try {
- const response = await fetch('/jsxsd/jxzl/jxzl_query', {
- method: 'POST', credentials: 'same-origin', signal: controller.signal,
- body: new URLSearchParams({ xnxq01id: semester })
- });
- if (!response.ok) throw new Error('教学周历请求失败');
- const doc = new DOMParser().parseFromString(await response.text(), 'text/html');
- const selected = doc.querySelector('#xnxq01id');
- if (selected && selected.value !== semester) throw new Error('教学周历学期不匹配');
- const rows = [...doc.querySelectorAll('#kbtable tr')].filter(row => row.querySelector('td'));
- const match = rows[0]?.querySelector('td[title]')?.getAttribute('title')?.match(/^(\d{4})年(\d{2})月(\d{2})日?$/);
- const weeks = rows.map(row => parseInt(row.querySelector('td').textContent.trim(), 10)).filter(n => n > 0 && n <= 100);
- if (!match || !weeks.length) throw new Error('无法解析教学周历');
- const date = `${match[1]}-${match[2]}-${match[3]}`;
- const parsed = new Date(`${date}T00:00:00Z`);
- if (isNaN(parsed) || parsed.toISOString().slice(0, 10) !== date) throw new Error('教学周历日期无效');
- return { semesterStartDate: date, semesterTotalWeeks: Math.max(...weeks), firstDayOfWeek: 1 };
- } finally { clearTimeout(timer); }
- }
- const timeSlots = [
- ['08:00','08:45'], ['08:50','09:35'], ['09:55','10:40'], ['10:45','11:30'],
- ['11:35','12:20'], ['14:00','14:45'], ['14:50','15:35'], ['15:55','16:40'],
- ['16:45','17:30'], ['19:00','19:45'], ['19:50','20:35'], ['20:40','21:25']
- ].map(([startTime, endTime], i) => ({ number: i + 1, startTime, endTime }));
- async function request(options) {
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), 20000);
- try {
- const response = await fetch(endpoint, { ...options, credentials: 'same-origin', signal: controller.signal });
- if (!response.ok) throw new Error(`教务请求失败:${response.status}`);
- const doc = new DOMParser().parseFromString(await response.text(), 'text/html');
- if (!doc.querySelector('#kbtable') || !doc.querySelector('#xnxq01id')) {
- throw new Error('登录已失效或课表结构不匹配,请重新进入教务课表页面');
- }
- return doc;
- } finally { clearTimeout(timer); }
- }
- function parseWeeks(text) {
- const odd = /单/.test(text), even = /双/.test(text);
- const source = text.replace(/(/g, '(').split('[')[0]
- .replace(/\([^)]*\)/g, '').replace(/[周单双\s]/g, '').replace(/,/g, ',');
- const weeks = new Set();
- for (const part of source.split(',')) {
- const match = part.match(/^(\d+)(?:-(\d+))?$/);
- if (!match) throw new Error('无法识别周次');
- const start = Number(match[1]), end = Number(match[2] || match[1]);
- if (start < 1 || end < start || end > 100) throw new Error('周次超出有效范围');
- for (let w = start; w <= end; w++) if ((!odd || w % 2 === 1) && (!even || w % 2 === 0)) weeks.add(w);
- }
- if (!weeks.size) throw new Error('周次为空');
- return [...weeks].sort((a, b) => a - b);
- }
- try {
- if (location.hostname !== 'jwxt.wit.edu.cn') throw new Error('请先进入武汉工程大学教务系统');
- const initial = await request();
- const select = initial.querySelector('#xnxq01id');
- const semesters = [...select.options].filter(option => option.value);
- const index = await bridge.showSingleSelection('选择学期', JSON.stringify(semesters.map(o => o.textContent.trim())), semesters.findIndex(o => o.selected));
- if (index === null) return;
- if (!semesters[index]) throw new Error('学期选择无效');
- const semester = semesters[index].value;
- const body = new URLSearchParams();
- for (const name of ['jx0404id', 'cj0701id', 'demo', 'kbjcmsid']) body.set(name, initial.querySelector(`[name="${name}"]`)?.value || '');
- body.set('xnxq01id', semester);
- body.set('zc', '');
- const detail = initial.querySelector('[name="sfFD"]');
- if (detail?.checked) body.set('sfFD', detail.value);
- const doc = await request({ method: 'POST', body });
- if (doc.querySelector('#xnxq01id').value !== semester || doc.querySelector('#zc')?.value) throw new Error('返回学期或周次与查询条件不一致');
- const table = doc.querySelector('#kbtable');
- const headers = [...table.rows[0].cells].map(c => c.textContent.trim());
- const days = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
- const courses = new Map();
- let invalid = 0;
- for (const row of [...table.rows].slice(1)) {
- for (let column = 0; column < row.cells.length; column++) {
- const cell = row.cells[column];
- for (const div of cell.querySelectorAll('div.kbcontent')) {
- if (!div.textContent.trim()) continue;
- for (const html of div.innerHTML.split(/(?:<br\s*\/?\s*>\s*)?-{5,}(?:\s*<br\s*\/?\s*>)?/i)) {
- const block = doc.createElement('div'); block.innerHTML = html;
- if (!block.textContent.trim()) continue;
- try {
- const day = days.indexOf(headers[column]) + 1;
- const timing = block.querySelector('font[title="周次(节次)"]')?.textContent || '';
- const section = timing.match(/\[([\d,,\-\s]+)节\]/);
- const name = [...block.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent.trim()).find(Boolean);
- if (!name || !day || !section) throw new Error('课程信息不完整');
- const weeks = parseWeeks(timing);
- const teacher = block.querySelector('font[title="老师"],font[title="教师"]')?.textContent.trim() || '';
- const position = block.querySelector('font[title="教室"]')?.textContent.trim() || '';
- for (const part of section[1].replace(/,/g, ',').split(',')) {
- const match = part.trim().match(/^(\d+)(?:-(\d+))?$/);
- if (!match) throw new Error('节次无法识别');
- const startSection = Number(match[1]), endSection = Number(match[2] || match[1]);
- if (startSection < 1 || endSection < startSection || endSection > 50) throw new Error('节次无效');
- const key = JSON.stringify([name, teacher, position, day, startSection, endSection]);
- const old = courses.get(key);
- courses.set(key, { name, teacher, position, day, startSection, endSection, weeks: [...new Set([...(old?.weeks || []), ...weeks])].sort((a,b) => a-b) });
- }
- } catch (_) { invalid++; }
- }
- }
- }
- }
- if (invalid) throw new Error(`${invalid} 条课程无法完整解析,已停止导入,请反馈页面样本`);
- if (!courses.size) {
- await bridge.showAlert('该学期暂无课程', `${semester} 没有已排定时间的课程,本次未导入数据。可重新运行脚本选择其他学期。`, '知道了');
- return;
- }
- const remarks = [...table.rows].find(r => r.cells[0]?.textContent.includes('备注'))?.cells[1]?.textContent.trim();
- let config = null;
- try { config = await semesterInfo(semester); } catch (_) { /* 保留现有学期设置,提示用户核对。 */ }
- if (!await bridge.showAlert('导入前确认', `将导入 ${courses.size} 条课程安排,并写入学校作息。${config ? `教学周历:${config.semesterStartDate} 开始,共 ${config.semesterTotalWeeks} 周。` : '未能获取教学周历,请在 App 内手动核对开学日期和总周数。'}请核对节次作息。${remarks ? '\n课表含备注,请回教务核对其中未排定时间的课程并手动添加。' : ''}`, '确认导入')) return;
- if (config) await bridge.saveCourseConfig(JSON.stringify(config));
- await bridge.savePresetTimeSlots(JSON.stringify(timeSlots));
- await bridge.saveImportedCourses(JSON.stringify([...courses.values()]));
- toast(`成功导入 ${courses.size} 条课程安排`);
- window.shiguangBridge.notifyTaskCompletion();
- } catch (error) { toast(`导入停止:${error.message}`); }
- finally { window[lock] = false; }
- })();
|