// Enhanced Report list manager - 管理测评报告列表
class ReportListManager {
constructor() {
this.reports = [];
this.allReports = [];
this.currentPage = 1;
this.pageSize = 10;
this.totalPages = 1;
this.totalRecords = 0;
this.init();
}
async init() {
try {
await this.fetchReports();
this.render();
} catch (error) {
console.error('初始化失败:', error);
this.showError(`数据加载失败: ${error.message}`);
}
}
async fetchReports() {
try {
// 显示加载状态
document.getElementById('loading').style.display = 'block';
// 调用本地API获取报告数据
const response = await fetch(`/api/reports?page=${this.currentPage}&pageSize=${this.pageSize}`, {
method: 'GET',
headers: {
'content-type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const data = await response.json();
this.reports = data.reports || [];
this.totalRecords = data.total || 0;
this.totalPages = data.total_pages || 1;
this.currentPage = data.page || 1;
// 隐藏加载状态
document.getElementById('loading').style.display = 'none';
// 如果没有数据,显示空状态
if (this.reports.length === 0) {
document.getElementById('empty').style.display = 'block';
document.getElementById('table-content').style.display = 'none';
} else {
document.getElementById('empty').style.display = 'none';
document.getElementById('table-content').style.display = 'block';
}
} catch (error) {
console.error('获取报告失败:', error);
throw error;
}
}
render() {
this.renderTable();
this.renderPagination();
}
renderTable() {
const tbody = document.getElementById('report-list');
tbody.innerHTML = '';
this.reports.forEach((report, index) => {
const row = document.createElement('tr');
// 格式化日期
// 解析时间戳并转换为东八区时间
const dateObj = new Date(report.generated_at);
// 加8小时转换为东八区时间
const east8Time = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
// 格式化为 YYYY-MM-DD HH:MM
const year = east8Time.getFullYear();
const month = String(east8Time.getMonth() + 1).padStart(2, '0');
const day = String(east8Time.getDate()).padStart(2, '0');
const hours = String(east8Time.getHours()).padStart(2, '0');
const minutes = String(east8Time.getMinutes()).padStart(2, '0');
const date = `${year}-${month}-${day} ${hours}:${minutes}`;
row.innerHTML = `
${report.name} |
${date} |
${report.total_score}分 |
${report.grade} |
${report.school} |
查看报告
|
`;
tbody.appendChild(row);
});
}
renderPagination() {
const pagination = document.getElementById('pagination-left');
const pageInfo = document.querySelector('.page-info');
const controls = document.querySelector('.pagination-controls');
// 更新页面信息
pageInfo.textContent = `第 ${this.currentPage} 页,共 ${this.totalPages} 页`;
// 更新下拉列表
const pageSizeSelect = document.querySelector('.page-size-select');
if (pageSizeSelect) {
pageSizeSelect.value = this.pageSize;
}
// 生成页码按钮
controls.innerHTML = '';
// 上一页按钮
const prevBtn = document.createElement('button');
prevBtn.className = 'pagination-btn';
prevBtn.textContent = '上一页';
prevBtn.disabled = this.currentPage === 1;
prevBtn.onclick = () => this.goToPage(this.currentPage - 1);
controls.appendChild(prevBtn);
// 页码按钮
const startPage = Math.max(1, this.currentPage - 2);
const endPage = Math.min(this.totalPages, this.currentPage + 2);
if (startPage > 1) {
this.addPageButton(controls, 1);
if (startPage > 2) {
const ellipsis = document.createElement('span');
ellipsis.className = 'pagination-ellipsis';
ellipsis.textContent = '...';
controls.appendChild(ellipsis);
}
}
for (let i = startPage; i <= endPage; i++) {
this.addPageButton(controls, i);
}
if (endPage < this.totalPages) {
if (endPage < this.totalPages - 1) {
const ellipsis = document.createElement('span');
ellipsis.className = 'pagination-ellipsis';
ellipsis.textContent = '...';
controls.appendChild(ellipsis);
}
this.addPageButton(controls, this.totalPages);
}
// 下一页按钮
const nextBtn = document.createElement('button');
nextBtn.className = 'pagination-btn';
nextBtn.textContent = '下一页';
nextBtn.disabled = this.currentPage === this.totalPages;
nextBtn.onclick = () => this.goToPage(this.currentPage + 1);
controls.appendChild(nextBtn);
}
addPageButton(container, pageNum) {
const btn = document.createElement('button');
btn.className = `pagination-btn ${pageNum === this.currentPage ? 'active' : ''}`;
btn.textContent = pageNum;
btn.onclick = () => this.goToPage(pageNum);
container.appendChild(btn);
}
async goToPage(page) {
if (page < 1 || page > this.totalPages || page === this.currentPage) {
return;
}
this.currentPage = page;
await this.fetchReports();
this.render();
}
async changePageSize(pageSize) {
if (pageSize === this.pageSize) {
return;
}
this.pageSize = pageSize;
this.currentPage = 1;
await this.fetchReports();
this.render();
}
showError(message) {
const errorDiv = document.getElementById('error');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}
}
try {
const reportContent = JSON.parse(reportData.report);
// 将解析后的报告内容与原始数据合并
return { ...reportData, ...reportContent };
} catch (parseError) {
console.warn('解析report字段失败:', parseError);
return reportData;
}
}
return reportData;
});
} else if (Array.isArray(data)) {
// 如果是数组,直接使用
allReports = data;
} else if (data.reports && Array.isArray(data.reports)) {
// 如果有reports字段且是数组,使用reports
allReports = data.reports;
} else if (data.studentInfo) {
// 如果是单个报告对象,包装成数组
allReports = [data];
}
// 存储所有数据
this.allReports = allReports;
this.totalRecords = allReports.length;
this.totalPages = Math.ceil(this.totalRecords / this.pageSize);
// 应用分页
this.applyPagination();
// 隐藏加载状态
document.getElementById('loading').style.display = 'none';
} catch (error) {
document.getElementById('loading').style.display = 'none';
throw error;
}
}
applyPagination() {
// 计算当前页的数据
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
this.reports = this.allReports.slice(startIndex, endIndex);
}
render() {
const tableContent = document.getElementById('table-content');
const reportList = document.getElementById('report-list');
const emptyState = document.getElementById('empty');
if (this.reports.length === 0) {
tableContent.style.display = 'none';
emptyState.style.display = 'block';
return;
}
// 生成表格行
const rows = this.reports.map(report => this.createReportRow(report)).join('');
reportList.innerHTML = rows;
// 添加分页控件
this.renderPagination();
// 显示表格
tableContent.style.display = 'block';
emptyState.style.display = 'none';
}
createReportRow(report) {
// 从数据中提取学生信息
let name = this.extractNameFromAnswer(report);
let testDate = this.formatDate(report.create_at);
let subject = this.extractSubjectFromReport(report);
// 使用原始id作为报告ID
const reportId = report.id || this.generateReportId(report);
return `
| ${this.escapeHtml(name)} |
${this.escapeHtml(testDate)} |
查看报告
|
`;
}
generateReportId(report) {
// 如果有明确的ID,直接使用
if (report.id) {
return report.id;
}
// 否则基于学生信息生成一个简单的ID
const name = this.extractNameFromAnswer(report);
const testDate = report.create_at || '';
const subject = this.extractSubjectFromReport(report);
// 简单的哈希函数生成ID
const str = `${name}-${testDate}-${subject}`;
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为32位整数
}
return Math.abs(hash).toString();
}
extractNameFromAnswer(report) {
// 尝试从answer字段中提取姓名
try {
if (report.answer) {
const answerData = JSON.parse(report.answer);
// 遍历答案数据,查找包含姓名的题目
for (const [questionId, answer] of Object.entries(answerData)) {
// 假设姓名题目的答案格式为 {"xxx": "姓名"} 或 {"xxx": "姓名文本"}
if (typeof answer === 'object') {
for (const [optionId, value] of Object.entries(answer)) {
// 如果答案看起来像是姓名(2-4个中文字符)
if (typeof value === 'string' && /^[\u4e00-\u9fa5]{2,4}$/.test(value)) {
return value;
}
}
} else if (typeof answer === 'string' && /^[\u4e00-\u9fa5]{2,4}$/.test(answer)) {
// 如果答案直接是姓名字符串
return answer;
}
}
}
} catch (error) {
console.warn('解析answer字段失败:', error);
}
// 如果从answer中找不到,尝试从report字段中获取
if (report.report) {
try {
const reportData = typeof report.report === 'string' ? JSON.parse(report.report) : report.report;
if (reportData.studentInfo && reportData.studentInfo.name) {
return reportData.studentInfo.name;
}
} catch (error) {
console.warn('解析report字段失败:', error);
}
}
return '未知';
}
extractSubjectFromReport(report) {
// 尝试从report字段中提取科目
if (report.report) {
try {
const reportData = typeof report.report === 'string' ? JSON.parse(report.report) : report.report;
if (reportData.studentInfo && reportData.studentInfo.subject) {
return reportData.studentInfo.subject;
}
} catch (error) {
console.warn('解析report字段失败:', error);
}
}
return '未知科目';
}
formatDate(dateStr) {
// 处理日期格式
if (!dateStr) {
return '未知时间';
}
// 如果已经是中文格式(如:2025年10月5日),直接返回
if (dateStr.includes('年')) {
return dateStr;
}
// 处理标准日期格式(如:2025-10-05 17:04:26)
try {
const date = new Date(dateStr);
if (isNaN(date.getTime())) {
return dateStr; // 如果无法解析,返回原始字符串
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}年${month}月${day}日`;
} catch (error) {
return dateStr; // 如果出错,返回原始字符串
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
renderPagination() {
const paginationContainer = document.getElementById('pagination');
if (!paginationContainer) {
// 如果分页容器不存在,创建一个
const tableContainer = document.querySelector('.table-container');
const paginationDiv = document.createElement('div');
paginationDiv.id = 'pagination';
paginationDiv.className = 'pagination-container';
tableContainer.appendChild(paginationDiv);
}
const paginationEl = document.getElementById('pagination');
if (this.totalPages <= 1) {
paginationEl.style.display = 'none';
return;
}
paginationEl.style.display = 'flex';
paginationEl.innerHTML = this.createPaginationHTML();
}
createPaginationHTML() {
let html = '';
html += '';
return html;
}
changePageSize(newPageSize) {
this.pageSize = parseInt(newPageSize);
this.currentPage = 1; // 重置到第一页
// 重新计算总页数
this.totalPages = Math.ceil(this.totalRecords / this.pageSize);
// 应用分页
this.applyPagination();
// 重新渲染
this.render();
}
goToPage(page) {
if (page < 1 || page > this.totalPages || page === this.currentPage) {
return;
}
// 更新当前页码
this.currentPage = page;
// 应用分页
this.applyPagination();
// 重新渲染
this.render();
}
showError(message) {
document.getElementById('loading').style.display = 'none';
document.getElementById('error').style.display = 'block';
document.getElementById('error').textContent = message;
document.getElementById('table-content').style.display = 'none';
document.getElementById('empty').style.display = 'none';
}
}
// 示例数据生成器(用于测试和演示)
class SampleDataGenerator {
static generateSampleReports() {
return [
{
id: '001',
studentInfo: {
name: '张三',
testDate: '2024-01-15',
subject: '数学',
school: '实验小学'
}
},
{
id: '002',
studentInfo: {
name: '李四',
testDate: '2024-01-18',
subject: '语文',
school: '育才小学'
}
},
{
id: '003',
studentInfo: {
name: '王五',
testDate: '2024-01-20',
subject: '英语',
school: '希望小学'
}
}
];
}
}
// 测评报告跳转地址映射表
const reportUrls = [
// 一年级
{ grade: 'grade1', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade1', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade1', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 二年级
{ grade: 'grade2', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade2', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade2', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 三年级
{ grade: 'grade3', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade3', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade3', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 四年级
{ grade: 'grade4', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade4', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade4', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 五年级
{ grade: 'grade5', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade5', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade5', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 六年级
{ grade: 'grade6', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade6', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade6', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 七年级
{ grade: 'grade7', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'physics', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'chemistry', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'biology', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'history', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'geography', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade7', subject: 'politics', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 八年级
{ grade: 'grade8', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'physics', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'chemistry', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'biology', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'history', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'geography', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade8', subject: 'politics', url: 'http://120.26.23.172:1991/s/HxWL2e' },
// 九年级
{ grade: 'grade9', subject: 'math', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'chinese', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'english', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'physics', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'chemistry', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'biology', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'history', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'geography', url: 'http://120.26.23.172:1991/s/HxWL2e' },
{ grade: 'grade9', subject: 'politics', url: 'http://120.26.23.172:1991/s/HxWL2e' }
];
// 报告跳转管理器
class ReportJumpManager {
constructor() {
this.gradeSelect = document.getElementById('grade-select');
this.subjectSelect = document.getElementById('subject-select');
this.createReportBtn = document.getElementById('create-report-btn');
this.init();
}
init() {
// 绑定事件
this.createReportBtn.addEventListener('click', this.handleButtonClick.bind(this));
this.gradeSelect.addEventListener('change', this.handleGradeChange.bind(this));
this.subjectSelect.addEventListener('change', this.handleSubjectChange.bind(this));
}
// 查找对应的测评报告URL
findReportUrl(grade, subject) {
const report = reportUrls.find(r => r.grade === grade && r.subject === subject);
return report ? report.url : null;
}
// 显示提示消息
showMessage(message, type = 'info') {
// 创建提示元素
const messageEl = document.createElement('div');
messageEl.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
color: white;
font-size: 14px;
z-index: 1000;
max-width: 300px;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
`;
// 根据类型设置背景色
if (type === 'error') {
messageEl.style.background = '#e74c3c';
} else if (type === 'warning') {
messageEl.style.background = '#f39c12';
} else {
messageEl.style.background = '#2ecc71';
}
messageEl.textContent = message;
document.body.appendChild(messageEl);
// 显示动画
setTimeout(() => {
messageEl.style.opacity = '1';
messageEl.style.transform = 'translateX(0)';
}, 100);
// 3秒后自动消失
setTimeout(() => {
messageEl.style.opacity = '0';
messageEl.style.transform = 'translateX(100%)';
setTimeout(() => {
if (messageEl.parentNode) {
document.body.removeChild(messageEl);
}
}, 300);
}, 3000);
}
// 按钮点击事件处理
handleButtonClick() {
const selectedGrade = this.gradeSelect.value;
const selectedSubject = this.subjectSelect.value;
// 检查是否选择了年级和学科
if (!selectedGrade) {
this.showMessage('请选择年级', 'warning');
this.gradeSelect.focus();
return;
}
if (!selectedSubject) {
this.showMessage('请选择学科', 'warning');
this.subjectSelect.focus();
return;
}
// 查找对应的URL
const reportUrl = this.findReportUrl(selectedGrade, selectedSubject);
if (reportUrl) {
this.showMessage('正在跳转到测评报告页面...', 'info');
// 在新标签页中打开URL
setTimeout(() => {
window.open(reportUrl, '_blank');
}, 500);
} else {
this.showMessage('未找到对应的测评报告链接', 'error');
}
}
// 年级选择变化事件
handleGradeChange() {
// 如果已经选择了学科,检查是否存在对应的组合
if (this.subjectSelect.value) {
const reportUrl = this.findReportUrl(this.gradeSelect.value, this.subjectSelect.value);
if (!reportUrl) {
this.showMessage('该年级暂无此学科的测评报告', 'warning');
}
}
}
// 学科选择变化事件
handleSubjectChange() {
// 如果已经选择了年级,检查是否存在对应的组合
if (this.gradeSelect.value) {
const reportUrl = this.findReportUrl(this.gradeSelect.value, this.subjectSelect.value);
if (!reportUrl) {
this.showMessage('该学科在此年级暂无测评报告', 'warning');
}
}
}
}
// 初始化报告列表管理器和跳转管理器
let reportListManager; // 全局变量,用于分页控件访问
document.addEventListener('DOMContentLoaded', function() {
reportListManager = new ReportListManager();
new ReportJumpManager();
});
// 如果需要测试,可以使用以下代码替换实际的API调用
// document.addEventListener('DOMContentLoaded', function() {
// const manager = new ReportListManager();
// manager.reports = SampleDataGenerator.generateSampleReports();
// manager.render();
// });