// 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 += '
'; html += `共 ${this.totalRecords} 条记录`; html += '
'; html += '
'; html += ''; html += `'; html += '
'; html += '
'; html += '
'; // 上一页按钮 html += ``; // 页码按钮 const startPage = Math.max(1, this.currentPage - 2); const endPage = Math.min(this.totalPages, this.currentPage + 2); if (startPage > 1) { html += ``; if (startPage > 2) { html += '...'; } } for (let i = startPage; i <= endPage; i++) { html += ``; } if (endPage < this.totalPages) { if (endPage < this.totalPages - 1) { html += '...'; } html += ``; } // 下一页按钮 html += ``; html += ''; html += `第 ${this.currentPage} / ${this.totalPages} 页`; 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(); // });