// 文件: neu.js
/**
* 显示自定义学年学期选择对话框
* @returns {Promise<{semesterCode: string, xnxqdm: string, xqdm: string} | null>}
* 返回包含学期代码的对象,若取消则返回 null
*/
async function showCustomSemesterDialog() {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:10000;display:flex;align-items:center;justify-content:center';
const dialog = document.createElement('div');
dialog.style.cssText = 'background:white;padding:20px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.3);min-width:280px;text-align:center';
dialog.innerHTML = `
选择学年学期
—
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
const startYearInput = dialog.querySelector('#startYear');
const endYearInput = dialog.querySelector('#endYear');
const termSelect = dialog.querySelector('#termSelect');
const confirmBtn = dialog.querySelector('#confirmBtn');
const cancelBtn = dialog.querySelector('#cancelBtn');
const cleanup = () => document.body.removeChild(overlay);
confirmBtn.onclick = () => {
const start = parseInt(startYearInput.value, 10);
const end = parseInt(endYearInput.value, 10);
if (isNaN(start) || isNaN(end)) { alert('请输入有效年份'); return; }
const semesterNum = termSelect.value === 'fall' ? '1' : '2';
const semesterCode = `${start}-${end}-${semesterNum}`;
cleanup();
resolve({ semesterCode, xnxqdm: semesterCode, xqdm: '01' });
};
cancelBtn.onclick = () => { cleanup(); resolve(null); };
});
}
/**
* 显示学期选择(封装 showCustomSemesterDialog)
* @returns {Promise} 返回学期代码字符串,取消则返回 false
*/
async function showSemesterSelection() {
const res = await showCustomSemesterDialog();
return res ? res.semesterCode : false;
}
/**
* 显示校区选择对话框(通过Android原生弹窗)
* @returns {Promise} 返回校区名称("南湖校区"或"浑南校区"),取消返回 false
*/
async function showCampusSelection() {
const campuses = ["南湖校区", "浑南校区"];
try {
const idx = await window.AndroidBridgePromise.showSingleSelection("选择你所在的校区", JSON.stringify(campuses), 2);
return idx !== -1 ? campuses[idx] : false;
} catch(e) {
AndroidBridge.showToast("显示校区列表出错:" + e.message);
return false;
}
}
/**
* 弹窗询问用户是否导入考试时间(测试功能)
* @returns {Promise} true-导入,false-不导入
*/
async function askImportExams() {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:10000;display:flex;align-items:center;justify-content:center';
const dialog = document.createElement('div');
dialog.style.cssText = 'background:white;padding:20px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.3);min-width:280px;text-align:center';
dialog.innerHTML = `
是否导入考试时间
测试功能,周数默认为第15周,需手动调整到对应日期。出错请反馈
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
const cleanup = () => document.body.removeChild(overlay);
dialog.querySelector('#yesBtn').onclick = () => { cleanup(); resolve(true); };
dialog.querySelector('#noBtn').onclick = () => { cleanup(); resolve(false); };
});
}
/**
* 解析考试时间描述字符串,提取星期几、开始时间、结束时间
* @param {string} desc 例如 "2026年05月06日 10:10-12:10(星期三第1场)"
* @returns {{day: number|null, startTime: string|null, endTime: string|null}}
* day: 1~7 对应星期一~星期日,无法解析则为 null
*/
function parseExamTimeDescription(desc) {
const weekMap = { '星期一': 1, '星期二': 2, '星期三': 3, '星期四': 4, '星期五': 5, '星期六': 6, '星期日': 7 };
let day = null;
let startTime = null;
let endTime = null;
for (const [cn, num] of Object.entries(weekMap)) {
if (desc.includes(cn)) {
day = num;
break;
}
}
const timeMatch = desc.match(/(\d{1,2}:\d{2})-(\d{1,2}:\d{2})/);
if (timeMatch) {
startTime = timeMatch[1];
endTime = timeMatch[2];
}
return { day, startTime, endTime };
}
/**
* 从考试API获取指定学期的考试数据,并转换为课程对象格式
* @param {string} termCode 学期代码,如 "2025-2026-1"
* @returns {Promise>} 课程对象数组,每个对象包含 name, teacher, position, day, weeks, isCustomTime, customStartTime, customEndTime
* @throws 网络或API错误
*/
async function fetchExamsFromAPI(termCode) {
const url = `https://jwxt.neu.edu.cn/jwapp/sys/homeapp/api/home/student/exams.do?termCode=${encodeURIComponent(termCode)}`;
const response = await fetch(url, {
method: 'GET',
headers: { 'Fetch-Api': 'true', 'Referer': 'https://jwxt.neu.edu.cn/jwapp/sys/homeapp/home/index.html', 'User-Agent': navigator.userAgent }
});
if (!response.ok) throw new Error(`考试API HTTP ${response.status}`);
const data = await response.json();
if (data.code !== '0') throw new Error(`考试API错误码: ${data.code}`);
const exams = data.datas || [];
const lessons = [];
for (const exam of exams) {
const rawName = exam.courseName || "";
const examType = exam.examType || "考试";
const desc = exam.examTimeDescription || "";
let dateStr = "";
const dateMatch = desc.match(/(\d{2})年(\d{2})月(\d{2})日/);
if (dateMatch) {
dateStr = `${dateMatch[2]}月${dateMatch[3]}日`;
} else {
const simpleMatch = desc.match(/(\d{2})月(\d{2})日/);
if (simpleMatch) dateStr = `${simpleMatch[1]}月${simpleMatch[2]}日`;
}
const name = dateStr ? `${rawName}_${examType}_${dateStr}` : `${rawName}_${examType}`;
const teacher = exam.teachers || "";
const position = exam.examPlace || "";
const { day, startTime, endTime } = parseExamTimeDescription(desc);
if (!day || !startTime || !endTime) {
console.warn("解析考试时间失败,跳过:", desc);
continue;
}
const weeks = [15]; // 考试固定在第15周(测试功能)
lessons.push({
name: name,
teacher: teacher,
position: position,
day: day,
startSection: undefined,
endSection: undefined,
weeks: weeks,
isCustomTime: true,
customStartTime: startTime,
customEndTime: endTime
});
}
return lessons;
}
/**
* 增强版周次解析:支持 "1-8周", "2-6周(双)", "1,3,5周" 等格式
* @param {string} weeksStr 周次字符串,如 "1-8周"
* @returns {number[]} 周次数字数组(已去重、排序)
*/
function parseWeeksString(weeksStr) {
if (!weeksStr) return [];
const result = [];
const weekParts = weeksStr.split(/[,,]/).map(part => part.trim());
weekParts.forEach(part => {
// 匹配单个数字周,如 "6周" 或 "6周(单)"
const singleMatch = part.match(/^(\d+)周(?:\(([单双])\))?$/);
if (singleMatch) {
const num = parseInt(singleMatch[1]);
const type = singleMatch[2];
if (!type || (type === '单' && num % 2 === 1) || (type === '双' && num % 2 === 0)) {
result.push(num);
}
return;
}
// 匹配范围周,如 "1-8周" 或 "2-6周(双)"
const rangeMatch = part.match(/^(\d+)-(\d+)周(?:\(([单双])\))?$/);
if (rangeMatch) {
const start = parseInt(rangeMatch[1]);
const end = parseInt(rangeMatch[2]);
const type = rangeMatch[3];
if (!type) {
for (let i = start; i <= end; i++) result.push(i);
} else if (type === '单') {
for (let i = start; i <= end; i++) {
if (i % 2 === 1) result.push(i);
}
} else if (type === '双') {
for (let i = start; i <= end; i++) {
if (i % 2 === 0) result.push(i);
}
}
}
});
return [...new Set(result)].sort((a, b) => a - b);
}
/**
* 将API返回的课表原始数据(arrangedList)转换为标准课程对象数组
* 新逻辑:直接从 titleDetail 解析课程名、周次、教师、地点
* @param {Array} arrangedList API返回的课表列表
* @returns {Array