survey/public/report-list.js
朱潮 99796408cf Initial commit: Add survey system with enhanced features
- Complete survey management system with web interface
- Question generation tools and prompts
- Report generation and analysis capabilities
- Docker configuration for deployment
- Database initialization scripts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 20:28:57 +08:00

777 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 = `
<td class="name-cell">${report.name}</td>
<td class="time-cell">${date}</td>
<td class="score-cell">${report.total_score}分</td>
<td class="grade-cell">${report.grade}</td>
<td class="school-cell">${report.school}</td>
<td class="action-cell">
<a href="/public/report.html?id=${report.id}" class="view-btn">查看报告</a>
</td>
`;
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 `
<tr>
<td class="name-cell">${this.escapeHtml(name)}</td>
<td class="time-cell">${this.escapeHtml(testDate)}</td>
<td class="action-cell">
<a href="report.html?id=${reportId}" class="view-btn">查看报告</a>
</td>
</tr>
`;
}
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 = '<div class="pagination-left">';
html += '<div class="pagination-info">';
html += `<span>共 ${this.totalRecords} 条记录</span>`;
html += '</div>';
html += '<div class="page-size-selector">';
html += '<label for="page-size">每页显示:</label>';
html += `<select id="page-size" class="page-size-select" onchange="reportListManager.changePageSize(this.value)">`;
const pageSizes = [5, 10, 20, 50];
pageSizes.forEach(size => {
html += `<option value="${size}" ${size === this.pageSize ? 'selected' : ''}>${size}条</option>`;
});
html += '</select>';
html += '</div>';
html += '</div>';
html += '<div class="pagination-controls">';
// 上一页按钮
html += `<button class="pagination-btn ${this.currentPage === 1 ? 'disabled' : ''}"
onclick="reportListManager.goToPage(${this.currentPage - 1})"
${this.currentPage === 1 ? 'disabled' : ''}>上一页</button>`;
// 页码按钮
const startPage = Math.max(1, this.currentPage - 2);
const endPage = Math.min(this.totalPages, this.currentPage + 2);
if (startPage > 1) {
html += `<button class="pagination-btn" onclick="reportListManager.goToPage(1)">1</button>`;
if (startPage > 2) {
html += '<span class="pagination-ellipsis">...</span>';
}
}
for (let i = startPage; i <= endPage; i++) {
html += `<button class="pagination-btn ${i === this.currentPage ? 'active' : ''}"
onclick="reportListManager.goToPage(${i})">${i}</button>`;
}
if (endPage < this.totalPages) {
if (endPage < this.totalPages - 1) {
html += '<span class="pagination-ellipsis">...</span>';
}
html += `<button class="pagination-btn" onclick="reportListManager.goToPage(${this.totalPages})">${this.totalPages}</button>`;
}
// 下一页按钮
html += `<button class="pagination-btn ${this.currentPage === this.totalPages ? 'disabled' : ''}"
onclick="reportListManager.goToPage(${this.currentPage + 1})"
${this.currentPage === this.totalPages ? 'disabled' : ''}>下一页</button>`;
html += '<span class="page-info">';
html += `${this.currentPage} / ${this.totalPages}`;
html += '</span>';
html += '</div>';
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();
// });