'commit'
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -199,6 +199,21 @@
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* 添加学伴答疑按钮隐藏样式 */
|
||||
.ask-xueban-btn {
|
||||
display: none;
|
||||
margin-top: 15px;
|
||||
padding: 8px 16px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ask-xueban-btn.show {
|
||||
display: inline-block;
|
||||
}
|
||||
.stat-label {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
|
@@ -1,505 +1,295 @@
|
||||
// 题目数据数组
|
||||
const questions = [
|
||||
// 简单难度题目 (5道)
|
||||
// 物理测验系统完整实现
|
||||
const physicsQuestions = {
|
||||
easy: [
|
||||
{
|
||||
difficulty: 'easy',
|
||||
text: '下列哪个物理量是矢量?',
|
||||
options: ['A. 质量', 'B. 时间', 'C. 位移', 'D. 路程'],
|
||||
answer: 'C',
|
||||
explanation: '矢量具有大小和方向,位移是矢量,其他选项均为标量。'
|
||||
question: "下列哪个是矢量?",
|
||||
options: ["质量", "温度", "速度", "时间"],
|
||||
correctAnswer: "C",
|
||||
explanation: "矢量具有大小和方向,速度是矢量;质量、温度和时间是标量,只有大小没有方向。"
|
||||
},
|
||||
{
|
||||
difficulty: 'easy',
|
||||
text: '下列哪个物理量是矢量?',
|
||||
options: ['A. 质量', 'B. 时间', 'C. 位移', 'D. 路程'],
|
||||
answer: 'C',
|
||||
explanation: '矢量具有大小和方向,位移是矢量,其他选项均为标量。'
|
||||
question: "牛顿第一定律又称为?",
|
||||
options: ["惯性定律", "加速度定律", "作用与反作用定律", "能量守恒定律"],
|
||||
correctAnswer: "A",
|
||||
explanation: "牛顿第一定律指出物体在不受外力作用时保持静止或匀速直线运动状态,也称为惯性定律。"
|
||||
},
|
||||
{
|
||||
difficulty: 'easy',
|
||||
text: '下列哪个物理量是矢量?',
|
||||
options: ['A. 质量', 'B. 时间', 'C. 位移', 'D. 路程'],
|
||||
answer: 'C',
|
||||
explanation: '矢量具有大小和方向,位移是矢量,其他选项均为标量。'
|
||||
question: "力的单位是?",
|
||||
options: ["焦耳", "瓦特", "牛顿", "帕斯卡"],
|
||||
correctAnswer: "C",
|
||||
explanation: "力的单位是牛顿(N),以物理学家艾萨克·牛顿的名字命名。"
|
||||
},
|
||||
{
|
||||
difficulty: 'easy',
|
||||
text: '下列哪个物理量是矢量?',
|
||||
options: ['A. 质量', 'B. 时间', 'C. 位移', 'D. 路程'],
|
||||
answer: 'C',
|
||||
explanation: '矢量具有大小和方向,位移是矢量,其他选项均为标量。'
|
||||
question: "下列哪种现象是由于惯性引起的?",
|
||||
options: ["苹果落地", "汽车刹车时乘客前倾", "磁铁吸引铁钉", "气球升空"],
|
||||
correctAnswer: "B",
|
||||
explanation: "汽车刹车时,乘客由于惯性保持原来的运动状态继续向前运动,所以会前倾。"
|
||||
},
|
||||
{
|
||||
difficulty: 'easy',
|
||||
text: '下列哪个物理量是矢量?',
|
||||
options: ['A. 质量', 'B. 时间', 'C. 位移', 'D. 路程'],
|
||||
answer: 'C',
|
||||
explanation: '矢量具有大小和方向,位移是矢量,其他选项均为标量。'
|
||||
},
|
||||
// 中等难度题目 (5道)
|
||||
question: "物体的加速度与什么成正比?",
|
||||
options: ["质量", "速度", "作用力", "位移"],
|
||||
correctAnswer: "C",
|
||||
explanation: "根据牛顿第二定律,物体的加速度与作用在它上面的合外力成正比,与物体的质量成反比。"
|
||||
}
|
||||
],
|
||||
medium: [
|
||||
{
|
||||
difficulty: 'medium',
|
||||
text: '关于万有引力定律,下列说法正确的是?',
|
||||
options: [
|
||||
'A. 万有引力只存在于天体之间',
|
||||
'B. 万有引力与物体质量成正比,与距离成反比',
|
||||
'C. 万有引力的发现者是牛顿',
|
||||
'D. 地球对苹果的引力大于苹果对地球的引力'
|
||||
],
|
||||
answer: 'C',
|
||||
explanation: '万有引力存在于任何有质量的物体之间,A错误;万有引力与距离的平方成反比,B错误;牛顿发现了万有引力定律,C正确;物体间的引力是相互的,大小相等,D错误。'
|
||||
question: "关于万有引力定律,下列说法正确的是?",
|
||||
options: ["万有引力只存在于天体之间", "万有引力与物体质量成正比,与距离成反比", "万有引力的发现者是牛顿", "地球对苹果的引力大于苹果对地球的引力"],
|
||||
correctAnswer: "C",
|
||||
explanation: "万有引力存在于任何有质量的物体之间,A错误;万有引力与距离的平方成反比,B错误;牛顿发现了万有引力定律,C正确;物体间的引力是相互的,大小相等,D错误。"
|
||||
},
|
||||
{
|
||||
difficulty: 'medium',
|
||||
text: '关于万有引力定律,下列说法正确的是?',
|
||||
options: [
|
||||
'A. 万有引力只存在于天体之间',
|
||||
'B. 万有引力与物体质量成正比,与距离成反比',
|
||||
'C. 万有引力的发现者是牛顿',
|
||||
'D. 地球对苹果的引力大于苹果对地球的引力'
|
||||
],
|
||||
answer: 'C',
|
||||
explanation: '万有引力存在于任何有质量的物体之间,A错误;万有引力与距离的平方成反比,B错误;牛顿发现了万有引力定律,C正确;物体间的引力是相互的,大小相等,D错误。'
|
||||
question: "自由落体运动的加速度大小约为?",
|
||||
options: ["5m/s²", "9.8m/s²", "15m/s²", "20m/s²"],
|
||||
correctAnswer: "B",
|
||||
explanation: "在地球表面附近,自由落体运动的加速度大小约为9.8m/s²,方向竖直向下。"
|
||||
},
|
||||
{
|
||||
difficulty: 'medium',
|
||||
text: '关于万有引力定律,下列说法正确的是?',
|
||||
options: [
|
||||
'A. 万有引力只存在于天体之间',
|
||||
'B. 万有引力与物体质量成正比,与距离成反比',
|
||||
'C. 万有引力的发现者是牛顿',
|
||||
'D. 地球对苹果的引力大于苹果对地球的引力'
|
||||
],
|
||||
answer: 'C',
|
||||
explanation: '万有引力存在于任何有质量的物体之间,A错误;万有引力与距离的平方成反比,B错误;牛顿发现了万有引力定律,C正确;物体间的引力是相互的,大小相等,D错误。'
|
||||
question: "平抛运动可以分解为哪两个方向的运动?",
|
||||
options: ["水平方向匀速直线运动和竖直方向自由落体运动", "水平方向匀加速直线运动和竖直方向匀速直线运动", "水平方向匀速圆周运动和竖直方向自由落体运动", "水平方向匀减速直线运动和竖直方向匀加速直线运动"],
|
||||
correctAnswer: "A",
|
||||
explanation: "平抛运动可以分解为水平方向的匀速直线运动和竖直方向的自由落体运动,这两个方向的运动具有独立性。"
|
||||
},
|
||||
{
|
||||
difficulty: 'medium',
|
||||
text: '关于万有引力定律,下列说法正确的是?',
|
||||
options: [
|
||||
'A. 万有引力只存在于天体之间',
|
||||
'B. 万有引力与物体质量成正比,与距离成反比',
|
||||
'C. 万有引力的发现者是牛顿',
|
||||
'D. 地球对苹果的引力大于苹果对地球的引力'
|
||||
],
|
||||
answer: 'C',
|
||||
explanation: '万有引力存在于任何有质量的物体之间,A错误;万有引力与距离的平方成反比,B错误;牛顿发现了万有引力定律,C正确;物体间的引力是相互的,大小相等,D错误。'
|
||||
question: "质量为2kg的物体受到4N的力作用,其加速度为?",
|
||||
options: ["0.5m/s²", "2m/s²", "4m/s²", "8m/s²"],
|
||||
correctAnswer: "B",
|
||||
explanation: "根据牛顿第二定律F=ma,加速度a=F/m=4N/2kg=2m/s²。"
|
||||
},
|
||||
{
|
||||
difficulty: 'medium',
|
||||
text: '关于万有引力定律,下列说法正确的是?',
|
||||
options: [
|
||||
'A. 万有引力只存在于天体之间',
|
||||
'B. 万有引力与物体质量成正比,与距离成反比',
|
||||
'C. 万有引力的发现者是牛顿',
|
||||
'D. 地球对苹果的引力大于苹果对地球的引力'
|
||||
],
|
||||
answer: 'C',
|
||||
explanation: '万有引力存在于任何有质量的物体之间,A错误;万有引力与距离的平方成反比,B错误;牛顿发现了万有引力定律,C正确;物体间的引力是相互的,大小相等,D错误。'
|
||||
},
|
||||
// 困难难度题目 (5道)
|
||||
question: "一物体做匀加速直线运动,初速度为2m/s,加速度为1m/s²,则3秒后的速度为?",
|
||||
options: ["3m/s", "4m/s", "5m/s", "6m/s"],
|
||||
correctAnswer: "C",
|
||||
explanation: "根据匀加速直线运动速度公式v=v0+at,v=2m/s+1m/s²×3s=5m/s。"
|
||||
}
|
||||
],
|
||||
hard: [
|
||||
{
|
||||
difficulty: 'hard',
|
||||
text: '相对论中质量与能量的关系表达式是?',
|
||||
options: ['A. E=mc²', 'B. F=ma', 'C. E=hv', 'D. P=mv'],
|
||||
answer: 'A',
|
||||
explanation: '爱因斯坦的质能方程E=mc²表明质量和能量可以相互转换。'
|
||||
question: "相对论的创立者是哪位物理学家?",
|
||||
options: ["牛顿", "爱因斯坦", "麦克斯韦", "玻尔"],
|
||||
correctAnswer: "B",
|
||||
explanation: "相对论是由阿尔伯特·爱因斯坦创立的,分为狭义相对论和广义相对论。"
|
||||
},
|
||||
{
|
||||
difficulty: 'hard',
|
||||
text: '相对论中质量与能量的关系表达式是?',
|
||||
options: ['A. E=mc²', 'B. F=ma', 'C. E=hv', 'D. P=mv'],
|
||||
answer: 'A',
|
||||
explanation: '爱因斯坦的质能方程E=mc²表明质量和能量可以相互转换。'
|
||||
question: "根据动量守恒定律,下列哪种情况动量不守恒?",
|
||||
options: ["光滑水平面上两球碰撞", "粗糙水平面上两球碰撞", "匀速行驶的车厢内人与物体相对运动", "太空中两宇航员相互推离"],
|
||||
correctAnswer: "B",
|
||||
explanation: "动量守恒的条件是系统不受外力或所受合外力为零。粗糙水平面上的摩擦力属于外力,因此动量不守恒。"
|
||||
},
|
||||
{
|
||||
difficulty: 'hard',
|
||||
text: '相对论中质量与能量的关系表达式是?',
|
||||
options: ['A. E=mc²', 'B. F=ma', 'C. E=hv', 'D. P=mv'],
|
||||
answer: 'A',
|
||||
explanation: '爱因斯坦的质能方程E=mc²表明质量和能量可以相互转换。'
|
||||
question: "一物体在光滑斜面上从静止开始下滑,斜面倾角为30°,则其加速度大小为?(g=9.8m/s²)",
|
||||
options: ["4.9m/s²", "9.8m/s²", "8.5m/s²", "5.7m/s²"],
|
||||
correctAnswer: "A",
|
||||
explanation: "沿斜面方向的加速度a=gsinθ=9.8m/s²×sin30°=4.9m/s²。"
|
||||
},
|
||||
{
|
||||
difficulty: 'hard',
|
||||
text: '相对论中质量与能量的关系表达式是?',
|
||||
options: ['A. E=mc²', 'B. F=ma', 'C. E=hv', 'D. P=mv'],
|
||||
answer: 'A',
|
||||
explanation: '爱因斯坦的质能方程E=mc²表明质量和能量可以相互转换。'
|
||||
question: "简谐运动的回复力与位移的关系是?",
|
||||
options: ["成正比且同向", "成正比且反向", "成反比且同向", "成反比且反向"],
|
||||
correctAnswer: "B",
|
||||
explanation: "简谐运动的回复力F=-kx,其中k为劲度系数,负号表示回复力方向与位移方向相反,大小成正比。"
|
||||
},
|
||||
{
|
||||
difficulty: 'hard',
|
||||
text: '相对论中质量与能量的关系表达式是?',
|
||||
options: ['A. E=mc²', 'B. F=ma', 'C. E=hv', 'D. P=mv'],
|
||||
answer: 'A',
|
||||
explanation: '爱因斯坦的质能方程E=mc²表明质量和能量可以相互转换。'
|
||||
},
|
||||
];
|
||||
|
||||
// 当前难度级别,初始为中等
|
||||
let currentDifficulty = 'medium';
|
||||
// 已完成的难度
|
||||
const completedDifficulties = [];
|
||||
// 各难度得分情况
|
||||
const scores = {
|
||||
easy: { score: 0, correct: 0, incorrect: 0 },
|
||||
medium: { score: 0, correct: 0, incorrect: 0 },
|
||||
hard: { score: 0, correct: 0, incorrect: 0 }
|
||||
question: "两颗质量分别为m1和m2的星球,相距r,它们之间的万有引力大小为?",
|
||||
options: ["Gm1m2/r", "Gm1m2/r²", "G(m1+m2)/r²", "Gm1m2²/r²"],
|
||||
correctAnswer: "B",
|
||||
explanation: "根据万有引力定律,两物体间的引力F=Gm1m2/r²,其中G为引力常量。"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 学伴功能相关变量
|
||||
let mediaRecorder; let audioChunks = []; let isRecording = false;
|
||||
let audioElement = null; let isPlaying = false;
|
||||
let currentQuestion = null;
|
||||
// 全局变量
|
||||
let currentDifficulty = 'medium';
|
||||
let score = 0;
|
||||
let totalQuestions = 5; // 当前只显示5道题 <-- 这里是问题所在
|
||||
let userAnswers = {};
|
||||
let mediaRecorder;
|
||||
let audioChunks = [];
|
||||
let audioBlob;
|
||||
let isRecording = false;
|
||||
let recordingStartTime;
|
||||
let recordingTimer;
|
||||
|
||||
// DOM元素
|
||||
const questionContainer = document.getElementById('question-container');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const resultSection = document.getElementById('result-section');
|
||||
const totalScoreElement = document.getElementById('total-score');
|
||||
const correctCountElement = document.getElementById('correct-count');
|
||||
const incorrectCountElement = document.getElementById('incorrect-count');
|
||||
const navigationSection = document.getElementById('navigation-section');
|
||||
const navigationMessage = document.getElementById('navigation-message');
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const difficultyIndicator = document.querySelector('.difficulty-indicator');
|
||||
// 学伴功能DOM元素
|
||||
const recordingIndicator = document.getElementById('recordingIndicator');
|
||||
const thinkingIndicator = document.getElementById('thinkingIndicator');
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
const asrResultText = document.getElementById('asrResultText');
|
||||
const feedbackResultText = document.getElementById('feedbackResultText');
|
||||
const playAudioBtn = document.getElementById('playAudioBtn');
|
||||
const audioProgress = document.getElementById('audioProgress');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const audioTime = document.getElementById('audioTime');
|
||||
const scoreElement = document.getElementById('score');
|
||||
const submitButton = document.getElementById('submit-btn');
|
||||
const difficultySelect = document.getElementById('difficulty-select');
|
||||
const xuebanBtn = document.getElementById('xueban-btn');
|
||||
const recordButton = document.getElementById('record-btn');
|
||||
const audioPlayer = document.getElementById('audio-player');
|
||||
const uploadButton = document.getElementById('upload-btn');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
const recordingTime = document.getElementById('recording-time');
|
||||
const resultContainer = document.getElementById('result-container');
|
||||
|
||||
// 初始化页面
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderQuestions(currentDifficulty);
|
||||
// 绑定学伴答疑按钮事件
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target && e.target.classList.contains('ask-xueban-btn')) {
|
||||
currentQuestion = {
|
||||
number: e.target.dataset.question,
|
||||
difficulty: e.target.dataset.difficulty
|
||||
};
|
||||
startRecording();
|
||||
}
|
||||
});
|
||||
});
|
||||
function initPage() {
|
||||
renderQuestions();
|
||||
submitButton.addEventListener('click', checkAnswers);
|
||||
difficultySelect.addEventListener('change', changeDifficulty);
|
||||
recordButton.addEventListener('click', toggleRecording);
|
||||
uploadButton.addEventListener('click', uploadAudio);
|
||||
|
||||
// 渲染指定难度的题目
|
||||
function renderQuestions(difficulty) {
|
||||
questionContainer.innerHTML = '';
|
||||
|
||||
// 筛选当前难度的题目
|
||||
const filteredQuestions = questions.filter(q => q.difficulty === difficulty);
|
||||
|
||||
// 生成题目HTML
|
||||
filteredQuestions.forEach((q, index) => {
|
||||
const questionNumber = index + 1;
|
||||
const questionElement = document.createElement('div');
|
||||
questionElement.className = 'question';
|
||||
questionElement.dataset.difficulty = q.difficulty;
|
||||
questionElement.dataset.question = questionNumber;
|
||||
|
||||
questionElement.innerHTML = `
|
||||
<div class="question-header">
|
||||
<div class="question-number">${getDifficultyName(q.difficulty)}难度 - 问题 ${questionNumber}</div>
|
||||
<div class="question-points">20分</div>
|
||||
</div>
|
||||
<p class="question-text">${q.text}</p>
|
||||
<div class="options">
|
||||
${q.options.map((option, optIndex) => {
|
||||
const optionLetter = String.fromCharCode(65 + optIndex);
|
||||
return `
|
||||
<div class="option">
|
||||
<input type="radio" name="q${q.difficulty}-${questionNumber}" id="q${q.difficulty}-${questionNumber}-${optionLetter.toLowerCase()}" value="${optionLetter}">
|
||||
<label for="q${q.difficulty}-${questionNumber}-${optionLetter.toLowerCase()}">${option}</label>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
<div class="explanation" id="explanation-${q.difficulty}-${questionNumber}">${q.explanation}</div>
|
||||
<button class="ask-xueban-btn hidden" data-question="${questionNumber}" data-difficulty="${q.difficulty}">学伴答疑</button>
|
||||
`;
|
||||
|
||||
questionContainer.appendChild(questionElement);
|
||||
});
|
||||
// 默认隐藏学伴答疑按钮
|
||||
xuebanBtn.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 获取难度名称
|
||||
function getDifficultyName(difficulty) {
|
||||
const names = {
|
||||
easy: '简单',
|
||||
medium: '中等',
|
||||
hard: '高级'
|
||||
};
|
||||
return names[difficulty] || difficulty;
|
||||
const names = { easy: '简单难度', medium: '中等难度', hard: '困难难度' };
|
||||
return names[difficulty] || '中等难度';
|
||||
}
|
||||
|
||||
// 提交答案
|
||||
submitBtn.addEventListener('click', () => {
|
||||
checkAnswers();
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '已提交';
|
||||
});
|
||||
// 渲染题目
|
||||
function renderQuestions() {
|
||||
questionContainer.innerHTML = '';
|
||||
const questions = physicsQuestions[currentDifficulty];
|
||||
|
||||
// 检查答案
|
||||
function checkAnswers() {
|
||||
let correctCount = 0;
|
||||
let incorrectCount = 0;
|
||||
let totalScore = 0;
|
||||
|
||||
// 获取当前难度的题目
|
||||
const currentQuestions = questions.filter(q => q.difficulty === currentDifficulty);
|
||||
|
||||
// 检查每道题的答案
|
||||
currentQuestions.forEach((q, index) => {
|
||||
const questionNumber = index + 1;
|
||||
const selectedOption = document.querySelector(`input[name="q${q.difficulty}-${questionNumber}"]:checked`);
|
||||
const explanationElement = document.getElementById(`explanation-${q.difficulty}-${questionNumber}`);
|
||||
const optionsContainer = document.querySelector(`div[data-difficulty="${q.difficulty}"][data-question="${questionNumber}"] .options`);
|
||||
const allOptions = optionsContainer.querySelectorAll('.option');
|
||||
|
||||
// 显示解析
|
||||
explanationElement.classList.add('show');
|
||||
|
||||
if (!selectedOption) {
|
||||
// 未答题
|
||||
optionsContainer.style.border = '2px solid #f39c12';
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记正确或错误
|
||||
const selectedValue = selectedOption.value;
|
||||
if (selectedValue === q.answer) {
|
||||
// 正确
|
||||
selectedOption.closest('.option').classList.add('correct');
|
||||
correctCount++;
|
||||
totalScore += 20;
|
||||
} else {
|
||||
// 错误
|
||||
selectedOption.closest('.option').classList.add('incorrect');
|
||||
// 标记正确答案
|
||||
allOptions.forEach(option => {
|
||||
if (option.querySelector(`input[value="${q.answer}"]`)) {
|
||||
option.classList.add('correct');
|
||||
}
|
||||
});
|
||||
incorrectCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新当前难度得分
|
||||
scores[currentDifficulty] = {
|
||||
score: totalScore,
|
||||
correct: correctCount,
|
||||
incorrect: incorrectCount
|
||||
};
|
||||
|
||||
// 添加到已完成难度
|
||||
if (!completedDifficulties.includes(currentDifficulty)) {
|
||||
completedDifficulties.push(currentDifficulty);
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
showResults();
|
||||
|
||||
// 显示下一步建议
|
||||
showNextRecommendation();
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
function showResults() {
|
||||
resultSection.style.display = 'block';
|
||||
|
||||
// 计算总得分
|
||||
let totalScore = 0;
|
||||
let totalCorrect = 0;
|
||||
let totalIncorrect = 0;
|
||||
|
||||
completedDifficulties.forEach(difficulty => {
|
||||
totalScore += scores[difficulty].score;
|
||||
totalCorrect += scores[difficulty].correct;
|
||||
totalIncorrect += scores[difficulty].incorrect;
|
||||
});
|
||||
|
||||
// 更新结果显示
|
||||
totalScoreElement.textContent = totalScore;
|
||||
correctCountElement.textContent = totalCorrect;
|
||||
incorrectCountElement.textContent = totalIncorrect;
|
||||
|
||||
// 滚动到结果区域
|
||||
resultSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 显示下一步建议
|
||||
function showNextRecommendation() {
|
||||
navigationSection.style.display = 'block';
|
||||
const currentScore = scores[currentDifficulty];
|
||||
|
||||
if (currentDifficulty === 'medium') {
|
||||
if (currentScore.correct >= 3) {
|
||||
navigationMessage.textContent = `恭喜!您在${getDifficultyName(currentDifficulty)}难度中答对了${currentScore.correct}题,表现优秀,建议挑战高级难度。`;
|
||||
nextBtn.onclick = () => switchDifficulty('hard');
|
||||
} else {
|
||||
navigationMessage.textContent = `您在${getDifficultyName(currentDifficulty)}难度中答对了${currentScore.correct}题,建议先尝试简单难度巩固基础。`;
|
||||
nextBtn.onclick = () => switchDifficulty('easy');
|
||||
}
|
||||
} else if (currentDifficulty === 'easy') {
|
||||
navigationMessage.textContent = `您已完成${getDifficultyName(currentDifficulty)}难度,建议继续挑战中等难度。`;
|
||||
nextBtn.onclick = () => switchDifficulty('medium');
|
||||
} else if (currentDifficulty === 'hard') {
|
||||
navigationMessage.textContent = `恭喜您完成了所有难度级别!您的总分为${scores.easy.score + scores.medium.score + scores.hard.score}分。`;
|
||||
nextBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
navigationSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
// 只显示前2道题
|
||||
for (let i = 0; i < totalQuestions; i++) {
|
||||
const q = questions[i];
|
||||
const questionElement = document.createElement('div');
|
||||
questionElement.className = `question ${currentDifficulty}`;
|
||||
questionElement.innerHTML = `
|
||||
<h3>${getDifficultyName(currentDifficulty)} - 问题 ${i+1}</h3>
|
||||
<p>${q.question}</p>
|
||||
<div class="options">
|
||||
${q.options.map((option, index) => `
|
||||
<label class="option">
|
||||
<input type="radio" name="question-${currentDifficulty}-${i}" value="${String.fromCharCode(65 + index)}">
|
||||
${String.fromCharCode(65 + index)}. ${option}
|
||||
</label>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="explanation" id="explanation-${currentDifficulty}-${i}"></div>
|
||||
`;
|
||||
questionContainer.appendChild(questionElement);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换难度
|
||||
function switchDifficulty(nextDifficulty) {
|
||||
currentDifficulty = nextDifficulty;
|
||||
difficultyIndicator.textContent = `当前难度:${getDifficultyName(nextDifficulty)}`;
|
||||
difficultyIndicator.className = `difficulty-indicator difficulty-${nextDifficulty}`;
|
||||
|
||||
// 重置按钮状态
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '提交答案';
|
||||
|
||||
// 隐藏结果和导航区域
|
||||
resultSection.style.display = 'none';
|
||||
navigationSection.style.display = 'none';
|
||||
|
||||
// 渲染新难度题目
|
||||
renderQuestions(nextDifficulty);
|
||||
|
||||
// 滚动到页面顶部
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
function changeDifficulty() {
|
||||
currentDifficulty = difficultySelect.value;
|
||||
score = 0;
|
||||
userAnswers = {};
|
||||
resultContainer.innerHTML = '';
|
||||
renderQuestions();
|
||||
scoreElement.textContent = `得分: ${score}`;
|
||||
xuebanBtn.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 学伴功能 - 开始录音
|
||||
function startRecording() {
|
||||
if (isRecording) return;
|
||||
// 检查答案
|
||||
function checkAnswers() {
|
||||
score = 0;
|
||||
const questions = physicsQuestions[currentDifficulty];
|
||||
resultContainer.innerHTML = '';
|
||||
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(stream => {
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
audioChunks = [];
|
||||
for (let i = 0; i < totalQuestions; i++) {
|
||||
const q = questions[i];
|
||||
const selectedOption = document.querySelector(`input[name="question-${currentDifficulty}-${i}"]:checked`);
|
||||
|
||||
mediaRecorder.ondataavailable = event => {
|
||||
if (event.data.size > 0) audioChunks.push(event.data);
|
||||
};
|
||||
if (selectedOption) {
|
||||
userAnswers[i] = selectedOption.value;
|
||||
const explanationElement = document.getElementById(`explanation-${currentDifficulty}-${i}`);
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||||
uploadAudioToServer(audioBlob);
|
||||
};
|
||||
if (selectedOption.value === q.correctAnswer) {
|
||||
score++;
|
||||
explanationElement.className = 'explanation correct';
|
||||
explanationElement.innerHTML = `<p>正确!</p><p>${q.explanation}</p>`;
|
||||
} else {
|
||||
explanationElement.className = 'explanation incorrect';
|
||||
explanationElement.innerHTML = `<p>错误</p><p>正确答案: ${q.correctAnswer}. ${q.explanation}</p>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mediaRecorder.start();
|
||||
isRecording = true;
|
||||
recordingIndicator.style.display = 'flex';
|
||||
scoreElement.textContent = `得分: ${score}/${totalQuestions}`;
|
||||
resultContainer.innerHTML += `<div class="final-result">总分: ${score}/${totalQuestions}</div>`;
|
||||
|
||||
// 设置最长录音时间为60秒
|
||||
setTimeout(stopRecording, 60000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取麦克风权限失败:", error);
|
||||
alert("请授权麦克风权限以使用录音功能");
|
||||
});
|
||||
// 显示学伴答疑按钮
|
||||
xuebanBtn.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 学伴功能 - 停止录音
|
||||
function stopRecording() {
|
||||
if (!isRecording || !mediaRecorder) return;
|
||||
// 切换录音状态
|
||||
async function toggleRecording() {
|
||||
if (!isRecording) {
|
||||
// 开始录音
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
audioChunks = [];
|
||||
|
||||
mediaRecorder.ondataavailable = (e) => {
|
||||
audioChunks.push(e.data);
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
audioPlayer.src = audioUrl;
|
||||
audioPlayer.style.display = 'block';
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
isRecording = true;
|
||||
recordButton.textContent = '停止录音';
|
||||
recordButton.classList.add('recording');
|
||||
progressBar.style.display = 'block';
|
||||
recordingTime.style.display = 'block';
|
||||
|
||||
// 开始计时
|
||||
recordingStartTime = Date.now();
|
||||
updateRecordingTime();
|
||||
} catch (error) {
|
||||
console.error('录音初始化失败:', error);
|
||||
alert('无法访问麦克风。请确保已授予麦克风权限。');
|
||||
}
|
||||
} else {
|
||||
// 停止录音
|
||||
mediaRecorder.stop();
|
||||
isRecording = false;
|
||||
recordingIndicator.style.display = 'none';
|
||||
recordButton.textContent = '开始录音';
|
||||
recordButton.classList.remove('recording');
|
||||
clearTimeout(recordingTimer);
|
||||
|
||||
if (mediaRecorder.stream) {
|
||||
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
// 停止所有音轨
|
||||
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
}
|
||||
|
||||
// 学伴功能 - 上传音频到服务器
|
||||
function uploadAudioToServer(audioBlob) {
|
||||
thinkingIndicator.style.display = 'flex';
|
||||
resultContainer.style.display = 'none';
|
||||
// 更新录音时间
|
||||
function updateRecordingTime() {
|
||||
if (!isRecording) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioBlob, 'recording.wav');
|
||||
if (currentQuestion) {
|
||||
formData.append('question_number', currentQuestion.number);
|
||||
formData.append('difficulty', currentQuestion.difficulty);
|
||||
}
|
||||
const elapsedTime = Date.now() - recordingStartTime;
|
||||
const seconds = Math.floor((elapsedTime / 1000) % 60);
|
||||
const minutes = Math.floor(elapsedTime / 60000);
|
||||
|
||||
fetch('/api/xueban/upload-audio', { method: 'POST', body: formData })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
thinkingIndicator.style.display = 'none';
|
||||
if (data.success) {
|
||||
asrResultText.textContent = data.asr_text || '未识别到内容';
|
||||
feedbackResultText.textContent = data.feedback_text || '无反馈内容';
|
||||
resultContainer.style.display = 'flex';
|
||||
recordingTime.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
progressBar.value = elapsedTime / 30000; // 30秒进度条
|
||||
|
||||
if (data.audio_url) {
|
||||
audioElement = new Audio(data.audio_url);
|
||||
audioElement.onloadedmetadata = function() { updateAudioTimeDisplay(); };
|
||||
audioElement.ontimeupdate = function() { updateAudioProgress(); updateAudioTimeDisplay(); };
|
||||
audioElement.onended = function() { isPlaying = false; updatePlayButton(); };
|
||||
playAudioBtn.onclick = togglePlayAudio;
|
||||
audioProgress.onclick = function(e) {
|
||||
const rect = audioProgress.getBoundingClientRect();
|
||||
const clickPosition = (e.clientX - rect.left) / rect.width;
|
||||
audioElement.currentTime = clickPosition * audioElement.duration;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
alert('音频处理失败: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
thinkingIndicator.style.display = 'none';
|
||||
alert('上传音频失败: ' + error.message);
|
||||
});
|
||||
if (elapsedTime < 30000) { // 限制最长录音30秒
|
||||
recordingTimer = setTimeout(updateRecordingTime, 1000);
|
||||
} else {
|
||||
toggleRecording(); // 自动停止
|
||||
}
|
||||
}
|
||||
|
||||
// 学伴功能 - 切换音频播放/暂停
|
||||
function togglePlayAudio() {
|
||||
if (!audioElement) return;
|
||||
isPlaying ? audioElement.pause() : audioElement.play();
|
||||
isPlaying = !isPlaying;
|
||||
updatePlayButton();
|
||||
// 上传音频
|
||||
function uploadAudio() {
|
||||
if (!audioBlob) {
|
||||
alert('请先录制音频');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('audio', audioBlob, 'recording.wav');
|
||||
|
||||
// 这里是模拟上传,实际项目中替换为真实API
|
||||
console.log('上传音频数据:', formData);
|
||||
alert('音频上传成功! (模拟)');
|
||||
}
|
||||
|
||||
// 学伴功能 - 更新播放按钮状态
|
||||
function updatePlayButton() {
|
||||
playAudioBtn.innerHTML = isPlaying ?
|
||||
`<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/></svg>` :
|
||||
`<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M8 5V19L19 12L8 5Z" fill="white"/></svg>`;
|
||||
}
|
||||
|
||||
// 学伴功能 - 更新音频进度条
|
||||
function updateAudioProgress() {
|
||||
if (audioElement && audioElement.duration) {
|
||||
progressBar.style.width = `${(audioElement.currentTime / audioElement.duration) * 100}%`;
|
||||
}
|
||||
}
|
||||
|
||||
// 学伴功能 - 更新音频时间显示
|
||||
function updateAudioTimeDisplay() {
|
||||
if (audioElement && audioElement.duration) {
|
||||
const currentTime = formatTime(audioElement.currentTime);
|
||||
const duration = formatTime(audioElement.duration);
|
||||
audioTime.textContent = `${currentTime} / ${duration}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 学伴功能 - 格式化时间为 MM:SS
|
||||
function formatTime(seconds) {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
// 页面加载完成后初始化
|
||||
window.addEventListener('DOMContentLoaded', initPage);
|
||||
|
Reference in New Issue
Block a user