删除返回首页
This commit is contained in:
parent
613ee8b1ad
commit
2a015e05dc
306
public/completion.html
Normal file
306
public/completion.html
Normal file
@ -0,0 +1,306 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{student_name}} - 测评已完成</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, #4CAF50, #45a049);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 30px;
|
||||
font-size: 40px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin: 20px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 10px 0;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
background: linear-gradient(135deg, #FF6B6B, #4ECDC4);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin: 30px 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.score-display::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: rgba(255,255,255,0.1);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.score-number {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
margin: 20px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 30px;
|
||||
margin: 10px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 25px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #6c757d, #5a6268);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 25px 15px;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
padding: 30px 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 45px;
|
||||
}
|
||||
|
||||
.report-status-container {
|
||||
padding: 25px 20px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.status-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 35px;
|
||||
margin-right: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.status-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
padding: 14px 25px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.secondary-info {
|
||||
font-size: 13px;
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="success-icon">✓</div>
|
||||
<h1>测评已完成!</h1>
|
||||
|
||||
<div class="student-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名:</span>
|
||||
<span class="info-value">{{student_name}}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">学校:</span>
|
||||
<span class="info-value">{{school}}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">年级:</span>
|
||||
<span class="info-value">{{grade}}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">完成时间:</span>
|
||||
<span class="info-value">{{completed_at}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="score-display">
|
||||
<div class="score-number">{{total_score}} 分</div>
|
||||
<div class="score-label">本次测评得分</div>
|
||||
</div>
|
||||
|
||||
<div class="message">
|
||||
恭喜您完成了本次学科能力测评!<br>
|
||||
系统正在为您生成详细的个性化报告,请稍后查看。
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a href="/quiz-results/{{session_id}}" class="btn btn-secondary">查看答题结果</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function checkAndShowReport(sessionId) {
|
||||
try {
|
||||
// 首先尝试通过session_id查找报告
|
||||
const response = await fetch('/api/reports');
|
||||
const data = await response.json();
|
||||
|
||||
const report = data.reports.find(r => r.session_id === sessionId);
|
||||
|
||||
if (report) {
|
||||
// 找到报告,跳转到报告页面
|
||||
window.location.href = `/report.html?id=${report.id}`;
|
||||
} else {
|
||||
// 报告还在生成中,显示提示
|
||||
alert('报告正在生成中,请稍后再试。\\n您也可以稍后返回首页查看历史报告。');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查报告失败:', error);
|
||||
alert('检查报告状态失败,请稍后再试。');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
学科能力测评系统 | 专业 · 科学 · 个性化
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
672
public/quiz-results.html
Normal file
672
public/quiz-results.html
Normal file
@ -0,0 +1,672 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{student_name}} - 答题结果详情</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
background: #f8f9fa;
|
||||
padding: 20px 30px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #4facfe;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.results-summary {
|
||||
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 50%, #ff7e5f 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.results-container {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background: white;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.question-card.correct {
|
||||
border-color: #4CAF50;
|
||||
box-shadow: 0 2px 15px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.question-card.incorrect {
|
||||
border-color: #f44336;
|
||||
box-shadow: 0 2px 15px rgba(244, 67, 54, 0.1);
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
margin-right: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.question-type.基础题 {
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
border: 1px solid #2e7d32;
|
||||
}
|
||||
|
||||
.question-type.进阶题 {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
border: 1px solid #f57c00;
|
||||
}
|
||||
|
||||
.question-type.竞赛题 {
|
||||
background: #fce4ec;
|
||||
color: #c2185b;
|
||||
border: 1px solid #c2185b;
|
||||
}
|
||||
|
||||
.result-status {
|
||||
margin-left: 10px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-status.correct {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-status.incorrect {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.options-list {
|
||||
list-style: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.option-item.selected-correct {
|
||||
background: #e8f5e8;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
.option-item.selected-incorrect {
|
||||
background: #ffebee;
|
||||
border-color: #f44336;
|
||||
}
|
||||
|
||||
.option-item.correct-answer {
|
||||
background: #e3f2fd;
|
||||
border-color: #2196F3;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.option-status {
|
||||
margin-left: 10px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.option-status.user-correct {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.option-status.user-incorrect {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.option-status.correct-answer {
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.answer-summary {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.answer-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.answer-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.answer-value.correct {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.answer-value.incorrect {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #ffebee;
|
||||
color: #f44336;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
margin: 0 10px;
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 25px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #6c757d, #5a6268);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #4CAF50, #45a049);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>📊 答题结果详情</h1>
|
||||
<p>查看每道题的答题情况和正确答案</p>
|
||||
</div>
|
||||
|
||||
<div class="student-info">
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">姓名</div>
|
||||
<div class="info-value">{{student_name}}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">学校</div>
|
||||
<div class="info-value">{{school}}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">年级</div>
|
||||
<div class="info-value">{{grade}}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">选题范围</div>
|
||||
<div class="info-value" id="selectedTagDisplay">正在加载...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results-summary">
|
||||
<div class="score-display">
|
||||
<div style="font-size: 36px; font-weight: bold;" id="totalScoreDisplay">正在计算...</div>
|
||||
<div style="font-size: 16px; opacity: 0.9; margin-top: 5px;">
|
||||
得分 / <span id="maxScoreDisplay">...</span>分
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-label">测评成绩</div>
|
||||
<div style="margin-top: 15px; font-size: 14px; opacity: 0.9;">
|
||||
<span id="correctCount">0</span> 题正确 /
|
||||
<span id="answeredCount">0</span> 题已答
|
||||
(共 <span id="totalQuestionsCount">0</span> 题)
|
||||
(正确率: <span id="accuracy">0</span>%)
|
||||
</div>
|
||||
<div class="actions" style="margin-top: 25px; padding: 0;">
|
||||
<button id="checkReportBtn" class="btn btn-success">查看详细报告</button>
|
||||
<button id="refreshBtn" class="btn btn-secondary">刷新页面</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results-container" id="resultsContainer">
|
||||
<div class="loading">正在加载答题结果...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class QuizResultsSystem {
|
||||
constructor() {
|
||||
this.sessionId = '{{session_id}}';
|
||||
this.results = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.loadResults();
|
||||
this.displayResults();
|
||||
} catch (error) {
|
||||
this.showError('加载答题结果失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async loadResults() {
|
||||
const response = await fetch(`/api/quiz-results/${this.sessionId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('无法获取答题结果');
|
||||
}
|
||||
this.results = await response.json();
|
||||
|
||||
if (!this.results.success) {
|
||||
throw new Error('答题结果数据无效');
|
||||
}
|
||||
}
|
||||
|
||||
displayResults() {
|
||||
// 更新学生信息
|
||||
const selectedTagDisplay = document.getElementById('selectedTagDisplay');
|
||||
if (selectedTagDisplay) {
|
||||
selectedTagDisplay.textContent = this.results.selectedTag || '全部题目';
|
||||
}
|
||||
|
||||
// 计算并显示统计信息
|
||||
const answeredQuestions = this.results.results.length;
|
||||
const correctQuestions = this.results.results.filter(r => r.isCorrect).length;
|
||||
const totalQuestions = this.results.totalQuestions || answeredQuestions;
|
||||
const accuracy = answeredQuestions > 0 ? Math.round((correctQuestions / answeredQuestions) * 100) : 0;
|
||||
|
||||
// 显示分数
|
||||
document.getElementById('totalScoreDisplay').textContent = this.results.totalScore + '分';
|
||||
document.getElementById('maxScoreDisplay').textContent = this.results.maxScore || 100;
|
||||
|
||||
// 显示题目统计
|
||||
document.getElementById('correctCount').textContent = correctQuestions;
|
||||
document.getElementById('answeredCount').textContent = answeredQuestions;
|
||||
document.getElementById('totalQuestionsCount').textContent = totalQuestions;
|
||||
document.getElementById('accuracy').textContent = accuracy;
|
||||
|
||||
// 显示题目详情
|
||||
this.renderQuestions();
|
||||
|
||||
// 根据报告状态显示按钮
|
||||
this.updateReportButton();
|
||||
|
||||
// 如果报告已生成,开始检查报告状态
|
||||
if (this.results.sessionStatus === 'report_generated' || this.results.sessionStatus === 'completed') {
|
||||
this.checkReportStatus();
|
||||
}
|
||||
}
|
||||
|
||||
renderQuestions() {
|
||||
const container = document.getElementById('resultsContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
this.results.results.forEach((result, index) => {
|
||||
const questionCard = document.createElement('div');
|
||||
questionCard.className = `question-card ${result.isCorrect ? 'correct' : 'incorrect'}`;
|
||||
|
||||
questionCard.innerHTML = `
|
||||
<div class="question-header">
|
||||
<div class="question-number">${result.questionNumber}</div>
|
||||
<div class="question-type ${result.questionType}">${result.questionType}</div>
|
||||
<div class="result-status ${result.isCorrect ? 'correct' : 'incorrect'}">
|
||||
${result.isCorrect ? '✓ 正确' : '✗ 错误'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-text">${result.questionText}</div>
|
||||
<ul class="options-list">
|
||||
${this.renderOptions(result)}
|
||||
</ul>
|
||||
<div class="answer-summary">
|
||||
<div>
|
||||
<span class="answer-label">你的答案:</span>
|
||||
<span class="answer-value ${result.isCorrect ? 'correct' : 'incorrect'}">${result.userAnswer || '未作答'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="answer-label">正确答案:</span>
|
||||
<span class="answer-value correct">${result.correctAnswer}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="answer-label">得分:</span>
|
||||
<span class="answer-value">${result.isCorrect ? result.score : 0} 分</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(questionCard);
|
||||
});
|
||||
}
|
||||
|
||||
renderOptions(result) {
|
||||
let optionsHTML = '';
|
||||
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||
|
||||
// 如果选项数据不存在或为空,显示提示信息
|
||||
if (!result.options || Object.keys(result.options).length === 0) {
|
||||
return '<li class="option-item" style="color: #666; font-style: italic;">选项数据暂不可用</li>';
|
||||
}
|
||||
|
||||
labels.forEach(label => {
|
||||
const optionText = result.options[label];
|
||||
if (optionText) {
|
||||
let optionClass = 'option-item';
|
||||
let statusBadge = '';
|
||||
|
||||
if (result.userAnswer === label && result.isCorrect) {
|
||||
optionClass += ' selected-correct';
|
||||
statusBadge = '<span class="option-status user-correct">你的答案 ✓</span>';
|
||||
} else if (result.userAnswer === label && !result.isCorrect) {
|
||||
optionClass += ' selected-incorrect';
|
||||
statusBadge = '<span class="option-status user-incorrect">你的答案 ✗</span>';
|
||||
} else if (result.correctAnswer === label && result.userAnswer !== label) {
|
||||
optionClass += ' correct-answer';
|
||||
statusBadge = '<span class="option-status correct-answer">正确答案 ✓</span>';
|
||||
}
|
||||
|
||||
optionsHTML += `
|
||||
<li class="${optionClass}">
|
||||
<span class="option-label">${label}</span>
|
||||
<span class="option-text">${optionText}</span>
|
||||
${statusBadge}
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有找到任何选项,显示提示信息
|
||||
if (!optionsHTML) {
|
||||
optionsHTML = '<li class="option-item" style="color: #666; font-style: italic;">选项数据格式异常</li>';
|
||||
}
|
||||
|
||||
return optionsHTML;
|
||||
}
|
||||
|
||||
async checkReportStatus() {
|
||||
// 如果报告已经生成,直接显示按钮
|
||||
if (this.results.sessionStatus === 'report_generated') {
|
||||
this.showReportButton();
|
||||
return;
|
||||
}
|
||||
|
||||
// 否则,先短暂等待,然后开始检查
|
||||
setTimeout(() => {
|
||||
this.startReportStatusCheck();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
updateReportButton() {
|
||||
const reportBtn = document.getElementById('checkReportBtn');
|
||||
|
||||
if (this.results.sessionStatus === 'report_generated') {
|
||||
// 报告已生成,按钮可点击
|
||||
this.showReportButton();
|
||||
} else {
|
||||
// 报告未生成,显示"报告生成中"且不可点击
|
||||
reportBtn.textContent = '报告生成中';
|
||||
reportBtn.disabled = true;
|
||||
reportBtn.style.opacity = '0.6';
|
||||
reportBtn.style.cursor = 'not-allowed';
|
||||
reportBtn.onclick = null;
|
||||
}
|
||||
}
|
||||
|
||||
showReportButton() {
|
||||
const reportBtn = document.getElementById('checkReportBtn');
|
||||
reportBtn.style.display = 'inline-block';
|
||||
reportBtn.textContent = '查看详细报告';
|
||||
reportBtn.disabled = false;
|
||||
reportBtn.style.opacity = '1';
|
||||
reportBtn.onclick = () => {
|
||||
// 跳转到报告页面,使用 session_id 作为参数
|
||||
window.location.href = `/report.html?session_id=${this.sessionId}`;
|
||||
};
|
||||
}
|
||||
|
||||
startReportStatusCheck() {
|
||||
let checkCount = 0;
|
||||
const maxChecks = 30;
|
||||
|
||||
const checkInterval = setInterval(async () => {
|
||||
checkCount++;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/reports');
|
||||
const data = await response.json();
|
||||
|
||||
const report = data.reports.find(r => r.session_id === this.sessionId);
|
||||
|
||||
if (report) {
|
||||
clearInterval(checkInterval);
|
||||
const reportBtn = document.getElementById('checkReportBtn');
|
||||
reportBtn.style.display = 'inline-block';
|
||||
reportBtn.textContent = '查看详细报告';
|
||||
reportBtn.onclick = () => {
|
||||
window.location.href = `/report.html?id=${report.id}`;
|
||||
};
|
||||
} else if (checkCount >= maxChecks) {
|
||||
clearInterval(checkInterval);
|
||||
// 超时后显示提示信息
|
||||
const reportBtn = document.getElementById('checkReportBtn');
|
||||
reportBtn.style.display = 'inline-block';
|
||||
reportBtn.textContent = '报告生成中';
|
||||
reportBtn.disabled = true;
|
||||
reportBtn.style.opacity = '0.6';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查报告状态失败:', error);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const container = document.getElementById('resultsContainer');
|
||||
container.innerHTML = `<div class="error">${message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化系统
|
||||
window.quizResults = new QuizResultsSystem();
|
||||
|
||||
// 刷新按钮事件
|
||||
document.getElementById('refreshBtn').addEventListener('click', () => {
|
||||
window.location.reload();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
800
public/quiz.html
Normal file
800
public/quiz.html
Normal file
@ -0,0 +1,800 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{student_name}} - 学科能力测评</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #4facfe;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #2d5a3d;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background: #e9ecef;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin: 20px 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.questions-container {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background: white;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-left: auto;
|
||||
border: 1px solid #2e7d32;
|
||||
}
|
||||
|
||||
.question-type.基础题 {
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
border: 1px solid #2e7d32;
|
||||
}
|
||||
|
||||
.question-type.进阶题 {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
border: 1px solid #f57c00;
|
||||
}
|
||||
|
||||
.question-type.竞赛题 {
|
||||
background: #fce4ec;
|
||||
color: #c2185b;
|
||||
border: 1px solid #c2185b;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.options-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.option-item:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #4facfe;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background: #e3f2fd;
|
||||
border-color: #4facfe;
|
||||
}
|
||||
|
||||
.option-item input[type="radio"] {
|
||||
margin-right: 12px;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #4CAF50, #66BB6A);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: block;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.submit-btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
text-align: center;
|
||||
padding: 40px 30px;
|
||||
display: none;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
border-radius: 20px;
|
||||
margin: 0 auto 30px;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.score-display::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, rgba(255,255,255,0.1) 0%, transparent 50%, rgba(255,255,255,0.1) 100%);
|
||||
animation: shimmer 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
50% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
100% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 60px;
|
||||
margin-bottom: 20px;
|
||||
animation: bounce 2s ease-in-out infinite;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
|
||||
40% { transform: translateY(-10px); }
|
||||
60% { transform: translateY(-5px); }
|
||||
}
|
||||
|
||||
.report-status-container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.status-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 40px;
|
||||
margin-right: 15px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.8; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.status-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 25px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
border-radius: 50%;
|
||||
animation: bounce-dots 1.4s ease-in-out infinite both;
|
||||
}
|
||||
|
||||
.loading-dot:nth-child(1) { animation-delay: -0.32s; }
|
||||
.loading-dot:nth-child(2) { animation-delay: -0.16s; }
|
||||
.loading-dot:nth-child(3) { animation-delay: 0s; }
|
||||
|
||||
@keyframes bounce-dots {
|
||||
0%, 80%, 100% { transform: scale(0); }
|
||||
40% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
background: linear-gradient(135deg, #4CAF50, #66BB6A);
|
||||
color: white;
|
||||
padding: 15px 30px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.primary-action:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.secondary-info {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #4facfe;
|
||||
}
|
||||
|
||||
.report-link {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #ff7e5f, #feb47b);
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.report-link:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(255, 126, 95, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🎯 学科能力测评</h1>
|
||||
<p>请认真回答以下问题</p>
|
||||
</div>
|
||||
|
||||
<div class="student-info">
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">姓名</div>
|
||||
<div class="info-value">{{student_name}}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">学校</div>
|
||||
<div class="info-value">{{school}}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">年级</div>
|
||||
<div class="info-value">{{grade}}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">选题范围</div>
|
||||
<div class="info-value" id="selectedTagDisplay">正在加载...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
|
||||
<div class="questions-container" id="questionsContainer">
|
||||
<div class="loading">正在加载题目...</div>
|
||||
</div>
|
||||
|
||||
<div class="result-section" id="resultSection">
|
||||
<div class="score-display">
|
||||
<div class="success-icon">🎉</div>
|
||||
<div class="score-value" id="scoreValue">0分</div>
|
||||
<div class="score-label">测评完成!</div>
|
||||
</div>
|
||||
|
||||
<div class="report-status-container">
|
||||
<div id="reportStatus">
|
||||
<div class="status-header">
|
||||
<div class="status-icon">⚡</div>
|
||||
<div>
|
||||
<div class="status-title">智能分析报告生成中</div>
|
||||
<div class="status-message">系统正在基于您的答题情况,AI正在深度分析并生成个性化学习建议...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading-indicator">
|
||||
<div class="loading-dots">
|
||||
<div class="loading-dot"></div>
|
||||
<div class="loading-dot"></div>
|
||||
<div class="loading-dot"></div>
|
||||
</div>
|
||||
<span style="margin-left: 15px; color: #4facfe; font-weight: 600;">AI正在分析中...</span>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="/quiz-results/{{session_id}}" class="primary-action">
|
||||
📊 立即查看答题结果
|
||||
</a>
|
||||
<div class="secondary-info">
|
||||
💡 您现在就可以查看详细的答题情况,AI报告生成完成后将在这里显示
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class QuizSystem {
|
||||
constructor() {
|
||||
this.sessionId = '{{session_id}}';
|
||||
this.questions = [];
|
||||
this.answers = {};
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.loadQuestions();
|
||||
this.renderQuestions();
|
||||
} catch (error) {
|
||||
this.showError('系统初始化失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async loadQuestions() {
|
||||
const response = await fetch(`/api/questions/{{session_id}}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('题目加载失败');
|
||||
}
|
||||
const data = await response.json();
|
||||
this.questions = data.questions;
|
||||
this.selectedTag = data.selectedTag;
|
||||
this.questionsConfig = data.questionsConfig;
|
||||
|
||||
// 更新选题范围显示
|
||||
const selectedTagDisplay = document.getElementById('selectedTagDisplay');
|
||||
if (selectedTagDisplay) {
|
||||
selectedTagDisplay.textContent = this.selectedTag || '全部题目';
|
||||
}
|
||||
|
||||
if (this.questions.length === 0) {
|
||||
throw new Error('没有可用的题目');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderQuestions() {
|
||||
const container = document.getElementById('questionsContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
this.questions.forEach((question, index) => {
|
||||
const questionCard = document.createElement('div');
|
||||
questionCard.className = 'question-card';
|
||||
questionCard.innerHTML = `
|
||||
<div class="question-header">
|
||||
<div class="question-number">${index + 1}</div>
|
||||
<div class="question-type ${question.questionType}">${question.questionType}</div>
|
||||
</div>
|
||||
<div class="question-text">${question['题干']}</div>
|
||||
<ul class="options-list">
|
||||
${this.renderOptions(question, index)}
|
||||
</ul>
|
||||
`;
|
||||
container.appendChild(questionCard);
|
||||
});
|
||||
|
||||
this.bindOptionEvents();
|
||||
}
|
||||
|
||||
renderOptions(question, questionIndex) {
|
||||
const options = [];
|
||||
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||
|
||||
// 通过遍历所有键来找到选项
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
let optionText = null;
|
||||
|
||||
// 遍历问题的所有键,寻找匹配的选项
|
||||
for (const key in question) {
|
||||
// 检查键是否包含当前选项字母(忽略空格数量)
|
||||
if (key.replace(/\s+/g, '').includes(`选项${labels[i]}`)) {
|
||||
optionText = question[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optionText && optionText.trim()) {
|
||||
options.push(`
|
||||
<li class="option-item" data-question="${questionIndex}" data-option="${labels[i]}">
|
||||
<input type="radio" name="question_${questionIndex}" value="${labels[i]}" id="option_${questionIndex}_${i}">
|
||||
<label for="option_${questionIndex}_${i}" class="option-text">${labels[i]}. ${optionText}</label>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
return options.join('');
|
||||
}
|
||||
|
||||
bindOptionEvents() {
|
||||
document.querySelectorAll('.option-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
const questionIndex = this.dataset.question;
|
||||
const option = this.dataset.option;
|
||||
const radio = this.querySelector('input[type="radio"]');
|
||||
|
||||
document.querySelectorAll(`.option-item[data-question="${questionIndex}"]`).forEach(otherItem => {
|
||||
otherItem.classList.remove('selected');
|
||||
});
|
||||
|
||||
this.classList.add('selected');
|
||||
radio.checked = true;
|
||||
|
||||
window.quiz.answers[questionIndex] = option;
|
||||
window.quiz.updateProgress();
|
||||
window.quiz.checkCompletion();
|
||||
});
|
||||
});
|
||||
|
||||
const container = document.getElementById('questionsContainer');
|
||||
const submitBtn = document.createElement('button');
|
||||
submitBtn.className = 'submit-btn';
|
||||
submitBtn.textContent = '提交答题';
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.onclick = () => this.submitQuiz();
|
||||
container.appendChild(submitBtn);
|
||||
}
|
||||
|
||||
updateProgress() {
|
||||
const total = this.questions.length;
|
||||
const answered = Object.keys(this.answers).length;
|
||||
const percentage = total > 0 ? (answered / total) * 100 : 0;
|
||||
|
||||
document.getElementById('progressFill').style.width = percentage + '%';
|
||||
}
|
||||
|
||||
checkCompletion() {
|
||||
const total = this.questions.length;
|
||||
const answered = Object.keys(this.answers).length;
|
||||
const submitBtn = document.querySelector('.submit-btn');
|
||||
submitBtn.disabled = answered !== total;
|
||||
}
|
||||
|
||||
async submitQuiz() {
|
||||
try {
|
||||
const submitBtn = document.querySelector('.submit-btn');
|
||||
submitBtn.textContent = '正在提交...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
const answersData = this.questions.map((question, index) => {
|
||||
// 提取完整的选项数据
|
||||
const options = {};
|
||||
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||
|
||||
for (const label of labels) {
|
||||
// 遍历问题的所有键,寻找匹配的选项
|
||||
for (const key in question) {
|
||||
// 检查键是否包含当前选项字母(忽略空格数量)
|
||||
if (key.replace(/\s+/g, '').includes(`选项${label}`)) {
|
||||
options[label] = question[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
questionId: question.questionId,
|
||||
questionText: question['题干'],
|
||||
questionType: question.questionType,
|
||||
userAnswer: this.answers[index] || '',
|
||||
correctAnswer: question['答案'],
|
||||
score: parseInt(question['分数']) || 0,
|
||||
options: options // 添加完整的选项数据
|
||||
};
|
||||
});
|
||||
|
||||
const response = await fetch('/api/save-answers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sessionId: this.sessionId,
|
||||
answers: answersData
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
this.showResults(result.totalScore);
|
||||
this.checkReportStatus();
|
||||
} else {
|
||||
throw new Error('提交失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('提交失败: ' + error.message);
|
||||
const submitBtn = document.querySelector('.submit-btn');
|
||||
submitBtn.textContent = '提交答题';
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
showResults(totalScore) {
|
||||
document.getElementById('questionsContainer').style.display = 'none';
|
||||
document.getElementById('resultSection').style.display = 'block';
|
||||
|
||||
// 获取总分信息并显示为"分数/总分"格式
|
||||
this.fetchSessionInfo().then(sessionInfo => {
|
||||
const maxScore = sessionInfo.maxScore || 100;
|
||||
document.getElementById('scoreValue').innerHTML =
|
||||
`<span style="font-size: 48px; font-weight: bold;">${totalScore}</span>` +
|
||||
`<span style="font-size: 24px; opacity: 0.8;"> / ${maxScore}分</span>`;
|
||||
}).catch(error => {
|
||||
// 如果获取失败,使用默认值
|
||||
document.getElementById('scoreValue').innerHTML =
|
||||
`<span style="font-size: 48px; font-weight: bold;">${totalScore}</span>` +
|
||||
`<span style="font-size: 24px; opacity: 0.8;"> / 100分</span>`;
|
||||
});
|
||||
|
||||
this.checkReportStatus();
|
||||
}
|
||||
|
||||
async fetchSessionInfo() {
|
||||
try {
|
||||
const response = await fetch(`/api/quiz-results/${this.sessionId}`);
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
return {
|
||||
maxScore: result.maxScore,
|
||||
totalQuestions: result.totalQuestions
|
||||
};
|
||||
}
|
||||
}
|
||||
// 如果API不可用,返回默认值
|
||||
return { maxScore: 100, totalQuestions: 14 };
|
||||
} catch (error) {
|
||||
console.warn('获取会话信息失败,使用默认值:', error);
|
||||
return { maxScore: 100, totalQuestions: 14 };
|
||||
}
|
||||
}
|
||||
|
||||
checkReportStatus() {
|
||||
// HTML已经包含完整的加载状态,不需要在这里设置
|
||||
// 继续检查报告生成状态
|
||||
let checkCount = 0;
|
||||
const maxChecks = 30;
|
||||
|
||||
const checkInterval = setInterval(async () => {
|
||||
checkCount++;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/reports');
|
||||
const data = await response.json();
|
||||
|
||||
const latestReport = data.reports.find(r => r.session_id === this.sessionId);
|
||||
|
||||
if (latestReport) {
|
||||
clearInterval(checkInterval);
|
||||
this.showReportLink(latestReport.id);
|
||||
} else if (checkCount >= maxChecks) {
|
||||
clearInterval(checkInterval);
|
||||
this.showTimeoutMessage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查报告状态失败:', error);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
showReportLink(reportId) {
|
||||
document.getElementById('reportStatus').innerHTML =
|
||||
`<div class="status-header">
|
||||
<div class="status-icon">🎯</div>
|
||||
<div>
|
||||
<div class="status-title">测评完成!</div>
|
||||
<div class="status-message">🎉 恭喜您完成测评!AI已为您生成了个性化的学习建议报告。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="/quiz-results/${this.sessionId}" class="primary-action">
|
||||
📊 查看答题结果
|
||||
</a>
|
||||
<a href="/report.html?id=${reportId}" class="primary-action" style="background: linear-gradient(135deg, #ff7e5f, #feb47b); box-shadow: 0 4px 15px rgba(255, 126, 95, 0.3);">
|
||||
🧠 查看AI智能报告
|
||||
</a>
|
||||
<div class="secondary-info">
|
||||
💡 建议先查看答题结果了解具体错题,再查看AI报告获得深度学习建议
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
showTimeoutMessage() {
|
||||
document.getElementById('reportStatus').innerHTML =
|
||||
`<div class="status-header">
|
||||
<div class="status-icon">⏰</div>
|
||||
<div>
|
||||
<div class="status-title">报告生成时间较长</div>
|
||||
<div class="status-message">当前用户较多,AI报告正在加急处理中,您可以先查看答题结果。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="/quiz-results/${this.sessionId}" class="primary-action">
|
||||
📊 查看答题结果
|
||||
</a>
|
||||
<div class="secondary-info">
|
||||
⏱️ AI报告完成后可在首页报告列表中查看,给您带来不便敬请谅解
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const container = document.getElementById('questionsContainer');
|
||||
container.innerHTML = `<div style="text-align: center; padding: 50px; color: #e74c3c; background: #ffebee; border-radius: 8px; margin: 20px;">${message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.quiz = new QuizSystem();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user