Vật lí Thầy Hùng

Hệ thống học tập vật lí trực tuyến
00 : 00 : 00
0 xu

Đang online

T Thầy Hùng

Thứ Hai, 21 tháng 7, 2025

Bài 20

Luyện Tập Vật Lý - Hệ Thống Học Tập

Thiết Lập Thời Gian Làm Bài

Thông tin thời gian làm bài:

  • Trắc nghiệm: 1 phút/câu
  • Đúng/Sai: 3 phút/câu
  • Tự luận: 4 phút/câu

Số câu ước tính: 20 câu

Luyện Tập Vật Lý - Hệ Thống Học Tập

Thiết Lập Thời Gian Làm Bài

Thông tin thời gian làm bài:

  • Trắc nghiệm: 1 phút/câu
  • Đúng/Sai: 3 phút/câu
  • Tự luận: 4 phút/câu

Số câu ước tính: 20 câu

/* ============================================ * PHẦN 2: TRẠNG THÁI ỨNG DỤNG VÀ DOM ELEMENTS * ========================================== */ const state = { currentQuestionIndex: 0, userAnswers: {}, startTime: null, timerInterval: null, questionKeys: [], totalQuestions: 0, extraKeys: [], currentExtraIndex: 0, userExtraAnswers: {}, userRating: 0, basicQuizSubmitted: false, advancedQuizSubmitted: false, examCode: null, // Thêm các trạng thái mới cho tính năng thời gian totalTimeAllowed: 0, // Tổng thời gian cho phép (giây) timeRemaining: 0, // Thời gian còn lại (giây) questionTimeRemaining: 0, // Thời gian còn lại cho câu hỏi hiện tại questionTimeLimit: 0, // Thời gian tối đa cho câu hỏi hiện tại selectedQuestions: [] // Danh sách câu hỏi được chọn }; const elements = { // Các phần tử hiện có timer: document.getElementById('timer'), progressBar: document.getElementById('progress-bar'), questionContainer: document.getElementById('question-container'), navigation: document.getElementById('navigation'), submitBtn: document.getElementById('submit-btn'), answerBtn: document.getElementById('answer-btn'), nextBtn: document.getElementById('next-btn'), extraExerciseBtn: document.getElementById('extra-exercise-btn'), extraExerciseContainer: document.getElementById('extra-exercise-container'), phoneModal: document.getElementById('phone-modal'), phoneInput: document.getElementById('phone-input'), phoneSubmit: document.getElementById('phone-submit'), phoneCancel: document.getElementById('phone-cancel'), checkResult: document.getElementById('check-result'), resultModal: document.getElementById('result-modal'), resultTime: document.getElementById('result-time'), resultMessage: document.getElementById('result-message'), commentBox: document.getElementById('comment-box'), ratingText: document.getElementById('rating-text'), stars: document.querySelectorAll('.star'), finalSubmit: document.getElementById('final-submit'), finalCancel: document.getElementById('final-cancel'), notification: document.getElementById('notification'), notificationText: document.getElementById('notification-text'), notificationClose: document.getElementById('notification-close'), submitForm: document.getElementById('main-submit-form'), guideModal: document.getElementById('guide-modal'), guideOkButton: document.getElementById('guide-ok'), // Thêm các phần tử mới setupContainer: document.getElementById('setup-container'), quizHeader: document.getElementById('quiz-header'), mainContent: document.getElementById('main-content'), totalTimeInput: document.getElementById('total-time'), questionEstimate: document.getElementById('question-estimate'), startBtn: document.getElementById('start-btn'), totalAllowedTime: document.getElementById('total-allowed-time') }; /* ============================================ * PHẦN 3: CÁC MODULE CHỨC NĂNG * ========================================== */ const saveCurrentQuestionState = (answerStateObject) => { const container = elements.questionContainer.querySelector('.question-container'); if (!container) return; const questionId = container.dataset.id; if (!questionId) return; container.querySelectorAll('.math-answer-field').forEach(mf => { const id = mf.dataset.id; const part = mf.dataset.part; const key = part ? `${id}_${part}` : id; if (mf.value.trim()) { answerStateObject[key] = [mf.value]; } else { delete answerStateObject[key]; } }); }; // Module mới: Quản lý thời gian và chọn câu hỏi const timeManager = { // Tính toán số câu hỏi dựa trên thời gian calculateQuestionCount(totalMinutes) { const totalSeconds = totalMinutes * 60; let remainingTime = totalSeconds; // Lấy tất cả câu hỏi có sẵn const availableQuestions = Object.values(quizData); // Sắp xếp câu hỏi theo độ ưu tiên (trắc nghiệm trước, rồi đến đúng/sai, cuối cùng là tự luận) const sortedQuestions = availableQuestions.sort((a, b) => { const priority = { multipleChoice: 1, trueFalse: 2, essay: 3 }; return priority[a.type] - priority[b.type]; }); // Chọn câu hỏi cho đến khi hết thời gian const selectedQuestions = []; for (const question of sortedQuestions) { const timeNeeded = config.TIME_PER_QUESTION[question.type]; if (remainingTime >= timeNeeded) { selectedQuestions.push(question); remainingTime -= timeNeeded; } } // Xáo trộn thứ tự câu hỏi return utils.shuffleArray(selectedQuestions); }, // Ước tính số câu hỏi dựa trên thời gian estimateQuestionCount(totalMinutes) { const totalSeconds = totalMinutes * 60; // Đếm số lượng từng loại câu hỏi có sẵn const availableCounts = { multipleChoice: Object.values(quizData).filter(q => q.type === 'multipleChoice').length, trueFalse: Object.values(quizData).filter(q => q.type === 'trueFalse').length, essay: Object.values(quizData).filter(q => q.type === 'essay').length }; // Tính số câu hỏi tối đa có thể làm cho mỗi loại const maxPossible = { multipleChoice: Math.min(availableCounts.multipleChoice, Math.floor(totalSeconds * 0.7 / config.TIME_PER_QUESTION.multipleChoice)), trueFalse: Math.min(availableCounts.trueFalse, Math.floor(totalSeconds * 0.2 / config.TIME_PER_QUESTION.trueFalse)), essay: Math.min(availableCounts.essay, Math.floor(totalSeconds * 0.1 / config.TIME_PER_QUESTION.essay)) }; return maxPossible.multipleChoice + maxPossible.trueFalse + maxPossible.essay; }, // Định dạng thời gian (giây -> MM:SS) formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }, // Cập nhật ước tính số câu hỏi updateQuestionEstimate() { const totalMinutes = parseInt(elements.totalTimeInput.value) || 20; const estimatedCount = this.estimateQuestionCount(totalMinutes); elements.questionEstimate.textContent = `Số câu ước tính: ${estimatedCount} câu`; } }; // Module hệ thống tính điểm const scoringSystem = { // Cải tiến normalizeExpression trong scoringSystem normalizeExpression(expr) { if (typeof expr !== 'string') expr = String(expr); if (!expr.trim()) return ''; return expr .toLowerCase() .replace(/\s+/g, '') .replace(/,/g, '.') // Xử lý các ký tự đặc biệt toán học .replace(/\\times/g, '*') .replace(/\\div/g, '/') .replace(/\\cdot/g, '*') .replace(/\\pi/g, 'pi') .replace(/\\infty/g, 'inf') .replace(/\\theta/g, 'theta') .replace(/\\alpha/g, 'alpha') .replace(/\\beta/g, 'beta') .replace(/\\gamma/g, 'gamma') // Loại bỏ đơn vị và ký tự thừa .replace(/(km\/h|cm\/s2|cm\/s|m\/s|rad\/s|cm|kg|hz|rad|gam|%)/g, '') // Chuẩn hóa phân số .replace(/(\d+)\/(\d+)/g, '($1)/($2)'); }, compareMathExpressions(userAnswer, correctAnswer) { if (userAnswer === undefined || userAnswer === null || correctAnswer === undefined || correctAnswer === null) { return false; } const userNorm = this.normalizeExpression(userAnswer); const correctNorm = this.normalizeExpression(correctAnswer); if (userNorm === correctNorm) return true; if (userNorm.includes(';') || correctNorm.includes(';')) { const userParts = userNorm.split(';').map(p => p.trim()).filter(p => p).sort(); const correctParts = correctNorm.split(';').map(p => p.trim()).filter(p => p).sort(); if (userParts.length !== correctParts.length) return false; return userParts.every((part, index) => this.compareMathExpressions(part, correctParts[index])); } try { const userValue = math.evaluate(userNorm); const correctValue = math.evaluate(correctNorm); const tolerance = Math.max(config.TOLERANCE, Math.abs(correctValue) * 0.01); return Math.abs(userValue - correctValue) < tolerance; } catch (e) { return userNorm === correctNorm; } }, checkEssayAnswer(questionPart, userAnswerArray) { if (!userAnswerArray || !userAnswerArray[0]) return false; const userAnswer = userAnswerArray[0]; return questionPart.answer.some(correctAns => this.compareMathExpressions(userAnswer, correctAns)); }, calculateQuestionScore(question, userAnswers) { if (!question || !userAnswers) return 0; switch (question.type) { case 'multipleChoice': const userAnswer = userAnswers[question.ID]; if (!userAnswer) return 0; const userChoiceIndex = userAnswer[0]; const correctChoiceIndex = question.choices.findIndex(c => c.answer === "true"); return (userChoiceIndex === correctChoiceIndex && correctChoiceIndex !== -1) ? (question.choices[correctChoiceIndex].score ?? 0.25) : 0; case 'essay': const userEssayAnswer = userAnswers[question.ID]; return this.checkEssayAnswer(question, userEssayAnswer) ? (question.score || 1) : 0; case 'essay-multi': let partScore = 0; Object.entries(question.parts).forEach(([partId, partData]) => { const userPartAnswer = userAnswers[`${question.ID}_${partId}`]; if (this.checkEssayAnswer(partData, userPartAnswer)) { partScore += partData.score || 1; } }); return partScore; case 'trueFalse': let tfScore = 0; const userSubAnswers = userAnswers[question.ID]; if (!userSubAnswers) return 0; question.subQuestions.forEach((subQ, index) => { const correctAnswer = subQ.answer === 'true'; const userSubAnswer = userSubAnswers[index]; if (userSubAnswer === correctAnswer) { tfScore += subQ.score ?? 0.1; } }); return tfScore; default: return 0; } }, calculateScore(questionKeys, questionData, userAnswers, maxScore) { let totalPossiblePoints = 0; let earnedPoints = 0; questionKeys.forEach(key => { const question = questionData[key]; if (!question) return; let questionMaxScore = 0; switch (question.type) { case 'multipleChoice': const correctChoice = question.choices.find(c => c.answer === "true"); questionMaxScore = correctChoice ? (correctChoice.score ?? 0.25) : 0.25; break; case 'essay': questionMaxScore = question.score || 1; break; case 'essay-multi': questionMaxScore = Object.values(question.parts).reduce((sum, part) => sum + (part.score || 1), 0); break; case 'trueFalse': questionMaxScore = question.subQuestions.reduce((sum, sub) => sum + (sub.score ?? 0.1), 0); break; } totalPossiblePoints += questionMaxScore; earnedPoints += this.calculateQuestionScore(question, userAnswers); }); if (totalPossiblePoints === 0) return 0; const normalizedScore = (earnedPoints / totalPossiblePoints) * maxScore; return Math.min(normalizedScore, maxScore); }, calculateBasicScore() { const questionKeys = state.selectedQuestions.map(q => q.ID); return this.calculateScore(questionKeys, quizData, state.userAnswers, 7); }, calculateAdvancedScore() { return this.calculateScore(state.extraKeys, extraPracticeData, state.userExtraAnswers, 3); } }; // Module tiện ích const utils = { playClickSound() { try { const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU9vT19XgS0BAgECAQIDAwMDBQQEBQYGBgcIBwcICAkKCgoLCwwMDAwNDg4ODw8PEA8PERERERISEhITExMTFBQUFBYWFhYXFxgYGCIXFRYVFBMTDxAODQwMCQgHBgUEAwIB'); audio.volume = 0.3; audio.play(); } catch(e) {} }, showNotification(message, isError = false) { elements.notificationText.textContent = message; elements.notification.className = 'notification'; if (isError) elements.notification.classList.add('error'); elements.notification.classList.add('show'); setTimeout(() => { elements.notification.classList.remove('show'); }, 5000); }, hideModal(modalElement) { if (modalElement) modalElement.style.display = 'none'; }, handleImageError(img) { img.onerror = null; const placeholder = document.createElement('div'); placeholder.className = 'image-placeholder'; placeholder.textContent = 'Lỗi khi tải hình ảnh.'; if (img.parentNode) img.parentNode.replaceChild(placeholder, img); }, shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; }, generateExamCode(length) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; }, preventCopyEvents() { document.addEventListener('contextmenu', (e) => { e.preventDefault(); this.showNotification('Tính năng này đã bị vô hiệu hóa trên bài làm', true); }); document.addEventListener('copy', (e) => { e.preventDefault(); this.showNotification('Không cho phép sao chép nội dung bài làm', true); }); document.addEventListener('cut', (e) => { e.preventDefault(); this.showNotification('Không cho phép cắt nội dung bài làm', true); }); } }; // ========================================================================== // * MODULE RENDER LATEX HOÀN CHỈNH (ĐÃ SỬA LỖI CACHE) // ======================================================================== const latexRenderer = { // THÊM: Cache để tối ưu hiệu suất cache: new Map(), // GIỮ NGUYÊN: Hàm render chính từ mã gốc (đã tích hợp cache) renderLatex(content) { if (!content || typeof content !== 'string') return content; // KIỂM TRA CACHE TRƯỚC if (this.cache.has(content)) { return this.cache.get(content); } try { // THÊM: Xử lý các ký tự đặc biệt trước let processed = content .replace(/\\\(/g, '(') // Chuyển \( thành ( .replace(/\\\)/g, ')') // Chuyển \) thành ) .replace(/\\\[/g, '[') // Chuyển \[ thành [ .replace(/\\\]/g, ']') // Chuyển \] thành ] // GIỮ NGUYÊN: Xử lý display math .replace(/\$\$([^$]+)\$\$/g, '
\\[$1\\]
') // GIỮ NGUYÊN: Xử lý inline math .replace(/\$([^$]+)\$/g, '\\($1\\)'); // LƯU VÀO CACHE this.cache.set(content, processed); // Giới hạn cache size (tránh memory leak) if (this.cache.size > 100) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } return processed; } catch (error) { console.error('Lỗi render LaTeX:', error); return content; } }, // GIỮ NGUYÊN: Hàm render an toàn cho các phần tử cụ thể safeRenderElement(element) { if (!element) return; const original = element.innerHTML; const rendered = this.renderLatex(original); if (rendered !== original) { element.innerHTML = rendered; this.triggerMathJax(); } }, // GIỮ NGUYÊN: Kích hoạt MathJax render - PHIÊN BẢN CẢI TIẾN triggerMathJax() { if (window.MathJax && window.MathJax.typesetPromise) { // Đảm bảo MathJax hoạt động đúng return MathJax.startup.promise.then(() => { return MathJax.typesetPromise().catch(err => { console.warn('MathJax typeset warning:', err); // Thử lại sau 100ms nếu có lỗi return new Promise(resolve => setTimeout(resolve, 100)) .then(() => MathJax.typesetPromise()); }); }).catch(err => { console.error('MathJax initialization error:', err); }); } return Promise.resolve(); }, // CẢI TIẾN: Kích hoạt MathJax với loading indicator triggerMathJaxWithLoading() { if (!window.MathJax) { console.warn('MathJax not loaded'); return Promise.resolve(); } // Thêm indicator loading const containers = document.querySelectorAll('.question-container, .choice-text'); containers.forEach(container => { container.classList.add('math-rendering'); }); return MathJax.startup.promise.then(() => { return MathJax.typesetPromise() .then(() => { containers.forEach(container => { container.classList.remove('math-rendering'); container.classList.add('math-rendered'); }); }) .catch(err => { console.error('MathJax typeset failed:', err); containers.forEach(container => { container.classList.remove('math-rendering'); container.classList.add('math-error'); }); throw err; }); }).catch(err => { console.error('MathJax initialization error:', err); containers.forEach(container => { container.classList.remove('math-rendering'); container.classList.add('math-error'); }); }); }, // Render container và kích hoạt MathJax - PHIÊN BẢN CẢI TIẾN safeRenderContainer(container) { if (!container) return Promise.resolve(); const originalContent = container.innerHTML; const renderedContent = this.renderLatex(originalContent); if (renderedContent !== originalContent) { container.innerHTML = renderedContent; // Kích hoạt MathJax sau khi render return this.triggerMathJax(); } return Promise.resolve(); }, // Render container (tương thích ngược) renderContainer(container) { if (!container) return; const originalContent = container.innerHTML; const renderedContent = this.renderLatex(originalContent); if (renderedContent !== originalContent) { container.innerHTML = renderedContent; // Kích hoạt MathJax sau khi render setTimeout(() => this.triggerMathJax(), 100); } }, // THÊM: Retry mechanism cho MathJax triggerMathJaxWithRetry(retries = 3) { return this.triggerMathJax() .catch(err => { if (retries > 0) { console.warn(`MathJax retry ${4-retries}/3`); return new Promise(resolve => setTimeout(resolve, 500)) .then(() => this.triggerMathJaxWithRetry(retries - 1)); } throw err; }); }, // THÊM: Hàm render với fallback safeRenderWithFallback(content) { try { return this.renderLatex(content); } catch (error) { console.warn('LaTeX render error, using fallback:', error); // Giữ nguyên nội dung gốc, chỉ xử lý các ký tự đặc biệt cơ bản return content .replace(/\$\$([^$]+)\$\$/g, '
$1
') .replace(/\$([^$]+)\$/g, '$1'); } }, // GIỮ NGUYÊN: Khởi tạo MathLive cho math-field initMathFields(container) { if (!container || !window.MathLive) return; const mathFields = container.querySelectorAll('math-field'); mathFields.forEach(mf => { if (!mf.hasAttribute('data-mathlive-initialized')) { try { MathLive.makeMathField(mf, { readOnly: mf.hasAttribute('readonly'), virtualKeyboardMode: 'manual', virtualKeyboards: 'numeric symbols' }); mf.setAttribute('data-mathlive-initialized', 'true'); } catch (error) { console.error('Lỗi khởi tạo MathLive:', error); } } }); }, // THÊM: Hàm clear cache (tùy chọn) clearCache() { this.cache.clear(); console.log('LaTeX cache cleared'); }, // THÊM: Hàm xem cache size (debug) getCacheSize() { return this.cache.size; } // XÓA: Hàm renderLatexWithCache vì đã tích hợp cache trong renderLatex }; // Module đồng hồ đếm ngược const timer = { startCountdown() { state.startTime = new Date(); state.timeRemaining = state.totalTimeAllowed; state.timerInterval = setInterval(() => { this.updateCountdown(); this.updateQuestionTimer(); }, 1000); }, updateCountdown() { if (state.timeRemaining <= 0) { this.stop(); if (!state.basicQuizSubmitted) { this.autoSubmit(); } return; } state.timeRemaining--; elements.timer.textContent = timeManager.formatTime(state.timeRemaining); // Cảnh báo khi thời gian sắp hết if (state.timeRemaining <= 60) { elements.timer.parentElement.style.background = 'rgba(220, 53, 69, 0.3)'; } }, updateQuestionTimer() { if (state.questionTimeRemaining > 0) { state.questionTimeRemaining--; const questionTimer = document.getElementById('question-timer'); if (questionTimer) { questionTimer.textContent = timeManager.formatTime(state.questionTimeRemaining); // Cảnh báo khi thời gian cho câu hỏi sắp hết if (state.questionTimeRemaining <= 30) { questionTimer.classList.add('warning'); } else { questionTimer.classList.remove('warning'); } } // Tự động chuyển câu khi hết thời gian if (state.questionTimeRemaining <= 0 && !state.basicQuizSubmitted) { questions.next(); } } }, stop() { clearInterval(state.timerInterval); state.timerInterval = null; }, getElapsedTime() { if (!state.startTime) return "00:00"; const elapsed = Math.floor((new Date() - state.startTime) / 1000); const totalAllowed = state.totalTimeAllowed; const usedTime = Math.min(elapsed, totalAllowed); const minutes = Math.floor(usedTime / 60).toString().padStart(2, '0'); const seconds = (usedTime % 60).toString().padStart(2, '0'); return `${minutes}:${seconds}`; }, // Tự động nộp bài khi hết thời gian autoSubmit() { utils.showNotification("Đã hết thời gian làm bài. Hệ thống tự động nộp bài.", true); quiz.finalizeBasicSubmission(); } }; // Module quản lý câu hỏi const questions = { display(index) { saveCurrentQuestionState(state.userAnswers); if (!state.basicQuizSubmitted) { this.checkAllAnswered(); } state.currentQuestionIndex = index; const question = state.selectedQuestions[index]; let questionHTML = ''; let questionText = question.mainQuestion; // XỬ LÝ HÌNH ẢNH VÀ LATEX TỐT HƠN const imgMatch = questionText.match(/]+>/); if (imgMatch) { const imgTag = imgMatch[0]; const parts = questionText.split(imgTag); // Xử lý LaTeX cho phần trước hình ảnh if (parts[0].trim()) { questionHTML += `
${latexRenderer.renderLatex(parts[0])}
`; } // Thêm hình ảnh questionHTML += imgTag; // Xử lý LaTeX cho phần sau hình ảnh if (parts[1] && parts[1].trim()) { questionHTML += `
${latexRenderer.renderLatex(parts[1])}
`; } } else { // XỬ LÝ LATEX CHO TOÀN BỘ CÂU HỎI questionHTML = `
${latexRenderer.renderLatex(questionText)}
`; } let html = `
Câu ${index + 1}/${state.totalQuestions} ${timeManager.formatTime(state.questionTimeLimit)}
${questionHTML}`; // Thiết lập thời gian cho câu hỏi hiện tại state.questionTimeRemaining = config.TIME_PER_QUESTION[question.type]; state.questionTimeLimit = config.TIME_PER_QUESTION[question.type]; switch(question.type) { case "multipleChoice": const mappedChoices = question.choices.map((choice, idx) => ({ choiceData: choice, originalIndex: idx })); const shuffledChoices = utils.shuffleArray(mappedChoices); html += `
`; shuffledChoices.forEach((c, i) => { const isSelected = (state.userAnswers[question.ID] || []).includes(c.originalIndex); // XỬ LÝ LATEX CHO LỰA CHỌN - SỬ DỤNG PHƯƠNG PHÁP MỚI const choiceContainer = document.createElement('div'); choiceContainer.innerHTML = c.choiceData.choice; latexRenderer.safeRenderElement(choiceContainer); html += `
${String.fromCharCode(65 + i)}
${choiceContainer.innerHTML}
`; }); html += `
`; break; case "essay": const userAnswerEssay = (state.userAnswers[question.ID] || [''])[0]; html += `${userAnswerEssay}`; break; case "trueFalse": html += `
`; question.subQuestions.forEach((subQ, subIndex) => { const userSubAnswer = state.userAnswers[question.ID] ? state.userAnswers[question.ID][subIndex] : null; const trueSelected = userSubAnswer === true ? 'selected' : ''; const falseSelected = userSubAnswer === false ? 'selected' : ''; // XỬ LÝ LATEX CHO CÂU HỎI PHỤ - SỬ DỤNG PHƯƠNG PHÁP MỚI const subQuestionContainer = document.createElement('div'); subQuestionContainer.innerHTML = subQ.question; latexRenderer.safeRenderElement(subQuestionContainer); html += `
${subQuestionContainer.innerHTML}
Đúng
Sai
`; }); html += `
`; break; } html += `
`; elements.questionContainer.innerHTML = html; // KÍCH HOẠT RENDER LATEX VÀ MATHLIVE - CẢI TIẾN setTimeout(() => { // Render LaTeX cho toàn bộ container latexRenderer.safeRenderContainer(elements.questionContainer); // Khởi tạo MathLive cho các math-field latexRenderer.initMathFields(elements.questionContainer); // Đảm bảo MathJax render hoàn tất latexRenderer.triggerMathJax(); }, 150); this.updateNavigationDots(); if (state.basicQuizSubmitted) { this.showAnswer(); } else { this.addAnswerEventListeners(); } }, addAnswerEventListeners() { const container = elements.questionContainer; const questionId = container.querySelector('.question-container')?.dataset.id; const question = state.selectedQuestions.find(q => q.ID == questionId); if (!questionId || !question) return; container.querySelectorAll(".choice").forEach(choice => { choice.addEventListener("click", e => { utils.playClickSound(); const index = parseInt(e.currentTarget.dataset.index); state.userAnswers[questionId] = [index]; this.updateNavigationAndProgress(); e.currentTarget.closest('.choices-container').querySelectorAll('.choice').forEach(c => c.classList.remove('selected')); e.currentTarget.classList.add('selected'); }); }); container.querySelectorAll(".math-answer-field").forEach(mf => { const handler = () => { saveCurrentQuestionState(state.userAnswers); this.updateNavigationAndProgress(); }; mf.addEventListener("input", handler); mf.addEventListener("blur", handler); }); if (question.type === 'trueFalse') { container.querySelectorAll('.tf-btn').forEach(btn => { btn.addEventListener('click', e => { utils.playClickSound(); const subIndex = parseInt(e.currentTarget.closest('.sub-question').dataset.subIndex); const value = e.currentTarget.dataset.value === 'true'; if (!state.userAnswers[questionId]) { state.userAnswers[questionId] = Array(question.subQuestions.length).fill(null); } state.userAnswers[questionId][subIndex] = value; e.currentTarget.parentElement.querySelectorAll('.tf-btn').forEach(b => b.classList.remove('selected')); e.currentTarget.classList.add('selected'); this.updateNavigationAndProgress(); }); }); } }, showAnswer() { const question = state.selectedQuestions[state.currentQuestionIndex]; const container = elements.questionContainer; const showAnswerForField = (field, isCorrect, correctAnswer) => { if (!field) return; field.readOnly = true; field.style.borderColor = isCorrect ? '#28a745' : '#dc3545'; field.style.backgroundColor = isCorrect ? '#f0fff4' : '#f8d7da'; if (!isCorrect && correctAnswer) { const correctAnsDiv = document.createElement('div'); correctAnsDiv.className = 'correct-answer-display'; // XỬ LÝ LATEX CHO ĐÁP ÁN ĐÚNG - PHƯƠNG PHÁP MỚI const answerContainer = document.createElement('div'); answerContainer.innerHTML = `Đáp án đúng: ${correctAnswer}`; latexRenderer.safeRenderElement(answerContainer); correctAnsDiv.innerHTML = `${answerContainer.innerHTML}`; field.parentElement.appendChild(correctAnsDiv); // Kích hoạt MathJax cho đáp án mới setTimeout(() => latexRenderer.triggerMathJax(), 50); } }; container.querySelectorAll('.choice, .tf-btn, math-field').forEach(el => { el.style.pointerEvents = 'none'; if (el.tagName === 'MATH-FIELD') el.readOnly = true; }); switch (question.type) { case 'multipleChoice': const correctIndex = question.choices.findIndex(c => c.answer === "true"); const userChoiceIndex = (state.userAnswers[question.ID] || [])[0]; container.querySelectorAll('.choice').forEach(choice => { const choiceOriginalIndex = parseInt(choice.dataset.index); choice.style.boxShadow = 'none'; if (choiceOriginalIndex === correctIndex) { choice.style.borderColor = '#28a745'; choice.style.backgroundColor = '#d4edda'; } else if (choiceOriginalIndex === userChoiceIndex) { choice.style.borderColor = '#dc3545'; choice.style.backgroundColor = '#f8d7da'; } }); break; case 'essay': const mf = container.querySelector(`math-field[data-id="${question.ID}"]`); const isEssayCorrect = scoringSystem.checkEssayAnswer(question, state.userAnswers[question.ID]); showAnswerForField(mf, isEssayCorrect, question.answer?.[0]); break; case 'trueFalse': const userSubAnswers = state.userAnswers[question.ID] || []; container.querySelectorAll('.sub-question').forEach((subEl, index) => { const correctAnswer = question.subQuestions[index].answer === 'true'; const userAnswer = userSubAnswers[index]; const trueBtn = subEl.querySelector('.tf-btn[data-value="true"]'); const falseBtn = subEl.querySelector('.tf-btn[data-value="false"]'); if (correctAnswer) { trueBtn.style.borderColor = '#28a745'; trueBtn.style.backgroundColor = '#d4edda'; } else { falseBtn.style.borderColor = '#28a745'; falseBtn.style.backgroundColor = '#d4edda'; } if (userAnswer !== undefined && userAnswer !== correctAnswer) { const wrongBtn = userAnswer ? trueBtn : falseBtn; wrongBtn.style.borderColor = '#dc3545'; wrongBtn.style.backgroundColor = '#f8d7da'; } }); break; } // KÍCH HOẠT LẠI MATHJAX SAU KHI HIỂN THỊ ĐÁP ÁN setTimeout(() => { latexRenderer.triggerMathJax(); }, 200); }, isBasicQuizFullyAnswered() { return state.selectedQuestions.every(q => this.isQuestionFullyAnswered(q.ID)); }, isQuestionStarted(key) { const question = state.selectedQuestions.find(q => q.ID == key); const answer = state.userAnswers[key]; if (!answer) return false; if (question.type === 'trueFalse') { return answer.some(subAnswer => subAnswer !== null); } return answer && answer[0] !== undefined && answer[0] !== null && String(answer[0]).trim() !== ''; }, isQuestionFullyAnswered(key) { const question = state.selectedQuestions.find(q => q.ID == key); const answer = state.userAnswers[key]; if (question.type === 'trueFalse') { return answer && answer.every(subAnswer => subAnswer !== null); } return answer && answer[0] !== undefined && answer[0] !== null && String(answer[0]).trim() !== ''; }, updateNavigationAndProgress() { this.updateNavigationDots(); this.updateProgressBar(); this.checkAllAnswered(); }, next() { if (state.currentQuestionIndex < state.totalQuestions - 1) { this.display(state.currentQuestionIndex + 1); } }, createNavigationDots() { elements.navigation.innerHTML = ''; state.selectedQuestions.forEach((_, i) => { const dot = document.createElement('div'); dot.className = 'nav-dot'; dot.textContent = i + 1; dot.dataset.index = i; dot.addEventListener('click', () => this.display(i)); elements.navigation.appendChild(dot); }); }, updateNavigationDots() { document.querySelectorAll('.nav-dot').forEach((dot, i) => { dot.classList.remove('active', 'answered'); if (i === state.currentQuestionIndex) dot.classList.add('active'); const question = state.selectedQuestions[i]; if (this.isQuestionFullyAnswered(question.ID)) { dot.classList.add('answered'); } }); }, updateProgressBar() { const answeredCount = state.selectedQuestions.filter(q => this.isQuestionFullyAnswered(q.ID)).length; const progress = (answeredCount / state.totalQuestions) * 100; elements.progressBar.style.width = `${progress}%`; }, checkAllAnswered() { if (this.isBasicQuizFullyAnswered()) { elements.submitBtn.disabled = false; elements.extraExerciseContainer.style.display = 'block'; } else { elements.submitBtn.disabled = true; elements.extraExerciseContainer.style.display = 'none'; } } }; // Module quản lý bài tập nâng cao const advancedExercises = { display(index) { saveCurrentQuestionState(state.userExtraAnswers); state.currentExtraIndex = index; const questionKey = state.extraKeys[index]; const question = extraPracticeData[questionKey]; // RENDER LATEX CHO CÂU HỎI CHÍNH const renderedQuestion = latexRenderer.renderLatex(question.mainQuestion); let html = `
Câu hỏi nâng cao ${index + 1}/${state.extraKeys.length}
${renderedQuestion}
`; switch(question.type) { case "multipleChoice": const mappedChoices = question.choices.map((choice, idx) => ({ choiceData: choice, originalIndex: idx })); const shuffledChoices = utils.shuffleArray(mappedChoices); html += `
`; shuffledChoices.forEach((c, i) => { const isSelected = (state.userExtraAnswers[question.ID] || []).includes(c.originalIndex); // RENDER LATEX CHO LỰA CHỌN const renderedChoice = latexRenderer.renderLatex(c.choiceData.choice); html += `
${String.fromCharCode(65 + i)}
${renderedChoice}
`; }); html += `
`; break; case "trueFalse": html += `
`; question.subQuestions.forEach((subQ, i) => { const userAnswer = (state.userExtraAnswers[question.ID] || [])[i]; // RENDER LATEX CHO CÂU HỎI ĐÚNG/SAI const renderedSubQuestion = latexRenderer.renderLatex(subQ.question); html += `
${renderedSubQuestion}
Đúng
Sai
`; }); html += `
`; break; case "essay": const userAnswer = (state.userExtraAnswers[question.ID] || [])[0] || ''; html += `
${userAnswer}
`; break; case "essay-multi": html += `
`; for (const [partId, partData] of Object.entries(question.parts)) { const partAnswer = (state.userExtraAnswers[`${question.ID}_${partId}`] || [])[0] || ''; // RENDER LATEX CHO CÂU HỎI PHẦN const renderedPartQuestion = latexRenderer.renderLatex(partData.question); html += `
${renderedPartQuestion}
${partAnswer}
`; } html += `
`; break; } html += `
`; elements.questionContainer.innerHTML = html; // KÍCH HOẠT RENDER LATEX VÀ MATHLIVE SAU KHI CHÈN HTML setTimeout(() => { latexRenderer.renderContainer(elements.questionContainer); latexRenderer.initMathFields(elements.questionContainer); }, 100); this.addAnswerEventListeners(); if (state.advancedQuizSubmitted) { this.showAnswer(); } }, addAnswerEventListeners() { const container = elements.questionContainer; const questionId = container.querySelector('.question-container')?.dataset.id; if (!questionId) return; // Xử lý sự kiện cho lựa chọn trắc nghiệm container.querySelectorAll(".choice").forEach(choice => { choice.addEventListener("click", e => { utils.playClickSound(); state.userExtraAnswers[questionId] = [parseInt(e.currentTarget.dataset.index)]; e.currentTarget.closest('.choices-container').querySelectorAll('.choice').forEach(c => c.classList.remove('selected')); e.currentTarget.classList.add('selected'); }); }); // Xử lý sự kiện cho đúng/sai container.querySelectorAll(".tf-btn").forEach(btn => { btn.addEventListener("click", e => { utils.playClickSound(); const stmtIndex = parseInt(e.currentTarget.dataset.index); const value = e.currentTarget.dataset.value === 'true'; if (!state.userExtraAnswers[questionId]) { state.userExtraAnswers[questionId] = []; } // Toggle selection const options = e.currentTarget.parentElement.querySelectorAll('.tf-btn'); options.forEach(opt => opt.classList.remove('selected')); e.currentTarget.classList.add('selected'); state.userExtraAnswers[questionId][stmtIndex] = value; }); }); // Xử lý sự kiện cho math-field (tự luận) container.querySelectorAll('math-field').forEach(mf => { if (!mf.hasAttribute('readonly')) { mf.addEventListener('input', (e) => { const fieldId = mf.id; if (fieldId.startsWith('essay-part-')) { // Xử lý essay_multi const parts = fieldId.split('-'); const qId = parts[2]; const partId = parts[3]; const key = `${qId}_${partId}`; state.userExtraAnswers[key] = [e.target.value]; } else if (fieldId.startsWith('essay-')) { // Xử lý essay thường const qId = fieldId.replace('essay-', ''); state.userExtraAnswers[qId] = [e.target.value]; } }); } }); }, showAnswer() { const questionKey = state.extraKeys[state.currentExtraIndex]; const question = extraPracticeData[questionKey]; const container = elements.questionContainer; container.querySelectorAll('.choice, math-field, .tf-btn').forEach(el => { el.style.pointerEvents = 'none'; if (el.tagName === 'MATH-FIELD') el.readOnly = true; }); switch (question.type) { case 'multipleChoice': const correctIndex = question.choices.findIndex(c => c.answer === "true"); const userChoiceIndex = (state.userExtraAnswers[question.ID] || [])[0]; container.querySelectorAll('.choice').forEach(choice => { const choiceOriginalIndex = parseInt(choice.dataset.index); choice.style.boxShadow = 'none'; if (choiceOriginalIndex === correctIndex) { choice.style.borderColor = '#28a745'; choice.style.backgroundColor = '#f0fff4'; } else if (choiceOriginalIndex === userChoiceIndex && userChoiceIndex !== correctIndex) { choice.style.borderColor = '#dc3545'; choice.style.backgroundColor = '#f8d7da'; } }); break; case 'trueFalse': container.querySelectorAll('.sub-question').forEach((subQ, index) => { const correctAnswer = question.subQuestions[index].answer === 'true'; const userAnswer = (state.userExtraAnswers[question.ID] || [])[index]; const options = subQ.querySelectorAll('.tf-btn'); options.forEach(option => { const optionValue = option.dataset.value === 'true'; if (optionValue === correctAnswer) { option.style.borderColor = '#28a745'; option.style.backgroundColor = '#f0fff4'; } else if (optionValue === userAnswer && userAnswer !== correctAnswer) { option.style.borderColor = '#dc3545'; option.style.backgroundColor = '#f8d7da'; } }); }); break; case 'essay': const userEssayAnswer = (state.userExtraAnswers[question.ID] || [])[0] || ''; const correctEssayAnswer = question.answer ? question.answer[0] : ''; if (correctEssayAnswer) { const answerDisplay = document.createElement('div'); answerDisplay.className = 'correct-answer-display'; answerDisplay.innerHTML = ` ✓ Đáp án: ${correctEssayAnswer} `; const essayContainer = container.querySelector('.essay-answer-container'); if (essayContainer) { essayContainer.appendChild(answerDisplay); // Khởi tạo MathLive cho đáp án setTimeout(() => { const answerField = answerDisplay.querySelector('math-field'); if (answerField) { MathLive.makeMathField(answerField, { readOnly: true }); } }, 50); } } break; case 'essay-multi': for (const [partId, partData] of Object.entries(question.parts)) { const correctPartAnswer = partData.answer ? partData.answer[0] : ''; if (correctPartAnswer) { const partContainer = container.querySelector(`#essay-part-${question.ID}-${partId}`)?.parentElement; if (partContainer) { const answerDisplay = document.createElement('div'); answerDisplay.className = 'correct-answer-display'; answerDisplay.innerHTML = ` ✓ Đáp án phần ${partId}: ${correctPartAnswer} `; partContainer.appendChild(answerDisplay); // Khởi tạo MathLive cho đáp án setTimeout(() => { const answerField = answerDisplay.querySelector('math-field'); if (answerField) { MathLive.makeMathField(answerField, { readOnly: true }); } }, 50); } } } break; } // Đảm bảo tất cả công thức toán được render lại setTimeout(() => { latexRenderer.triggerMathJax(); }, 200); }, next() { if (state.currentExtraIndex < state.extraKeys.length - 1) { this.display(state.currentExtraIndex + 1); } }, prev() { if (state.currentExtraIndex > 0) { this.display(state.currentExtraIndex - 1); } } }; // Module quản lý bài kiểm tra const quiz = { submitBasicOnly() { try { if (state.basicQuizSubmitted) return; saveCurrentQuestionState(state.userAnswers); if (!questions.isBasicQuizFullyAnswered()) { utils.showNotification("Vui lòng trả lời hết các câu hỏi.", true); return; } if (confirm("Bạn có chắc chắn muốn nộp bài ngay và chỉ tính điểm phần cơ bản?")) { this.finalizeBasicSubmission(); } } catch (error) { console.error("Lỗi khi nộp bài cơ bản:", error); utils.showNotification("Có lỗi xảy ra khi xử lý. Vui lòng thử lại.", true); } }, finalizeBasicSubmission() { try { timer.stop(); state.basicQuizSubmitted = true; const score = scoringSystem.calculateBasicScore(); this.toggleButtons(true); elements.answerBtn.disabled = false; elements.nextBtn.disabled = false; this.showResult(score, false); elements.extraExerciseContainer.style.display = 'block'; } catch (error) { console.error("Lỗi khi hoàn tất bài cơ bản:", error); utils.showNotification("Có lỗi khi xử lý kết quả", true); } }, showResult(score, isAdvanced) { try { if (!elements.resultModal || !elements.resultTime || !elements.resultMessage) { throw new Error("Thiếu phần tử DOM để hiển thị kết quả"); } this.setFormData(score, isAdvanced); elements.resultTime.textContent = timer.getElapsedTime(); elements.totalAllowedTime.textContent = timeManager.formatTime(state.totalTimeAllowed); if (isAdvanced) { const basicScore = scoringSystem.calculateBasicScore(); const advancedScore = scoringSystem.calculateAdvancedScore(); const totalScore = Math.min(basicScore + advancedScore, 10); elements.resultMessage.innerHTML = `

Mã đề: ${state.examCode}

Điểm phần cơ bản: ${basicScore.toFixed(2)}/7.00

Điểm phần nâng cao: ${advancedScore.toFixed(2)}/3.00

Tổng điểm: ${totalScore.toFixed(2)}/10.00

`; } else { elements.resultMessage.innerHTML = `

Mã đề: ${state.examCode}

Điểm phần cơ bản: ${score.toFixed(2)}/7.00

Bạn có thể làm thêm bài nâng cao để đạt tối đa 10 điểm

`; } elements.resultModal.style.display = 'flex'; questions.showAnswer(); } catch (error) { console.error("Lỗi khi hiển thị kết quả:", error); utils.showNotification("Không thể hiển thị kết quả", true); } }, toggleButtons(disable) { try { const buttons = ['submitBtn', 'advanced-submit-btn']; buttons.forEach(btnId => { const btn = document.getElementById(btnId) || elements[btnId]; if (btn) btn.disabled = disable; }); } catch (error) { console.error("Lỗi khi thay đổi trạng thái nút:", error); } }, setFormData(score, isAdvanced) { try { document.getElementById('hidden-result').value = `${score.toFixed(2)}/${isAdvanced ? '10' : '7'}`; document.getElementById('hidden-lesson').value = config.LESSON_TITLE || ''; document.getElementById('hidden-time').value = timer.getElapsedTime() || '00:00'; document.getElementById('hidden-semester').value = 'Học kì 1'; document.getElementById('hidden-exam-code').value = state.examCode; } catch (error) { console.error("Lỗi khi điền form:", error); } }, // Khởi tạo bài kiểm tra với thời gian đã chọn initializeQuiz() { const totalMinutes = parseInt(elements.totalTimeInput.value) || 20; if (totalMinutes < 5) { utils.showNotification("Thời gian làm bài tối thiểu là 5 phút", true); return; } // Chọn câu hỏi dựa trên thời gian state.selectedQuestions = timeManager.calculateQuestionCount(totalMinutes); state.totalQuestions = state.selectedQuestions.length; if (state.totalQuestions === 0) { utils.showNotification("Thời gian quá ngắn để làm bất kỳ câu hỏi nào", true); return; } // Thiết lập thời gian state.totalTimeAllowed = totalMinutes * 60; // Ẩn phần thiết lập, hiển thị phần làm bài elements.setupContainer.style.display = 'none'; elements.quizHeader.style.display = 'block'; elements.mainContent.style.display = 'block'; // Khởi tạo các thành phần state.examCode = utils.generateExamCode(4); timer.startCountdown(); questions.createNavigationDots(); questions.display(0); utils.showNotification(`Bài kiểm tra đã bắt đầu với ${state.totalQuestions} câu hỏi trong ${totalMinutes} phút`); } }; // Module đánh giá sao const rating = { init() { elements.stars.forEach(star => { star.addEventListener('click', () => { this.setRating(parseInt(star.dataset.value)); }); }); }, setRating(value) { state.userRating = value; elements.stars.forEach((star, index) => { star.classList.toggle('active', index < value); }); elements.ratingText.textContent = `Đánh giá: ${value}/5`; document.getElementById('hidden-rating').value = value; } }; /* ============================================ * PHẦN 4: KHỞI TẠO VÀ GẮN SỰ KIỆN * ========================================== */ function setupEventListeners() { // Sự kiện cho phần thiết lập thời gian elements.totalTimeInput.addEventListener('input', () => { timeManager.updateQuestionEstimate(); }); elements.startBtn.addEventListener('click', () => { utils.playClickSound(); quiz.initializeQuiz(); }); // Các sự kiện hiện có elements.submitBtn.addEventListener('click', () => quiz.submitBasicOnly()); elements.answerBtn.addEventListener('click', () => { if (state.basicQuizSubmitted) questions.showAnswer(); }); elements.nextBtn.addEventListener('click', () => questions.next()); elements.guideOkButton.addEventListener('click', () => { utils.playClickSound(); utils.hideModal(elements.guideModal); }); elements.finalCancel.addEventListener('click', () => utils.hideModal(elements.resultModal)); elements.submitForm.addEventListener('submit', handleFormSubmit); elements.notificationClose.addEventListener('click', () => elements.notification.classList.remove('show')); elements.extraExerciseBtn.addEventListener('click', () => { utils.playClickSound(); elements.phoneModal.style.display = 'flex'; }); elements.phoneCancel.addEventListener('click', () => { utils.playClickSound(); utils.hideModal(elements.phoneModal); }); elements.phoneSubmit.addEventListener('click', handlePhoneVerification); rating.init(); utils.preventCopyEvents(); // Khởi tạo ước tính số câu hỏi timeManager.updateQuestionEstimate(); } async function handlePhoneVerification() { utils.playClickSound(); const phone = elements.phoneInput.value.trim(); if (!phone || !/^\d{10}$/.test(phone)) { utils.showNotification("Vui lòng nhập số điện thoại hợp lệ (10 chữ số)", true); return; } elements.phoneSubmit.disabled = true; elements.phoneSubmit.innerHTML = '
Đang kiểm tra...'; elements.checkResult.innerHTML = ''; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 20000); const response = await fetch(`${config.PURCHASE_CHECK_URL}?phone=${phone}`, { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const result = await response.json(); if (!result) throw new Error('Invalid response from server'); const isPaid = Array.isArray(result) ? result.some(item => item.paid === true) : result.valid === true; if (isPaid) { utils.hideModal(elements.phoneModal); setupAdvancedQuizUI(); } else { elements.checkResult.innerHTML = '❌ SĐT không đúng. Vui lòng đăng ký.'; } } catch (error) { handlePhoneVerificationError(error); } finally { elements.phoneSubmit.disabled = false; elements.phoneSubmit.innerHTML = 'Kiểm Tra'; } } function setupAdvancedQuizUI() { const container = document.querySelector('.practice-container'); const mainContent = container.querySelector('.main-content'); const advancedHeader = document.createElement('div'); advancedHeader.className = 'quiz-header extra-header'; advancedHeader.innerHTML = `
BÀI TẬP NÂNG CAO
Chương: Chuyển động thẳng - Bài tập nâng cao
`; const oldHeader = container.querySelector('.quiz-header'); if(oldHeader) { container.replaceChild(advancedHeader, oldHeader); } mainContent.innerHTML = `
`; elements.questionContainer = document.getElementById('question-container'); // Khởi tạo câu hỏi nâng cao state.extraKeys = utils.shuffleArray(Object.keys(extraPracticeData)); advancedExercises.display(0); setupAdvancedControls(); } function setupAdvancedControls() { const backBtn = document.getElementById('advanced-back-btn'); const nextBtn = document.getElementById('advanced-next-btn'); const submitBtn = document.getElementById('advanced-submit-btn'); const answerBtn = document.getElementById('advanced-answer-btn'); const restartBtn = document.getElementById('back-to-basic-btn'); if (!backBtn || !nextBtn || !submitBtn || !answerBtn || !restartBtn) { console.error('Không tìm thấy một hoặc nhiều nút điều khiển nâng cao'); return; } backBtn.addEventListener('click', () => { utils.playClickSound(); advancedExercises.prev(); }); nextBtn.addEventListener('click', () => { utils.playClickSound(); advancedExercises.next(); }); submitBtn.addEventListener('click', handleAdvancedSubmit); answerBtn.addEventListener('click', () => { utils.playClickSound(); if(state.advancedQuizSubmitted) advancedExercises.showAnswer(); }); restartBtn.addEventListener('click', () => { if(confirm('Bạn có muốn làm lại bài từ đầu không?')) location.reload(); }); } function handleAdvancedSubmit() { utils.playClickSound(); const submitBtn = document.getElementById('advanced-submit-btn'); if (submitBtn.disabled) return; if (!confirm("Bạn có chắc chắn muốn nộp bài tập nâng cao và kết thúc bài làm?")) { return; } submitBtn.disabled = true; submitBtn.innerHTML = '
Đang xử lý...'; saveCurrentQuestionState(state.userExtraAnswers); if(state.timerInterval) timer.stop(); state.advancedQuizSubmitted = true; const basicScore = scoringSystem.calculateBasicScore(); const advancedScore = scoringSystem.calculateAdvancedScore(); const totalScore = Math.min(basicScore + advancedScore, 10); quiz.showResult(totalScore, true); advancedExercises.showAnswer(); const answerBtn = document.getElementById('advanced-answer-btn'); if (answerBtn) answerBtn.disabled = false; submitBtn.innerHTML = 'Đã Nộp'; } function handlePhoneVerificationError(error) { console.error('Phone verification error:', error); let errorMsg = '❌ Lỗi kết nối. Vui lòng thử lại.'; if (error.name === 'AbortError') { errorMsg = '❌ Yêu cầu hết thời gian. Vui lòng kiểm tra kết nối mạng.'; } elements.checkResult.innerHTML = `${errorMsg}`; } async function handleFormSubmit(event) { event.preventDefault(); const form = event.target; const submitBtn = elements.finalSubmit; if (!form['user-name']?.value || !form['user-email']?.value || !form['user-phone']?.value || !form['user-school']?.value || !form['user-class']?.value) { utils.showNotification('Vui lòng điền đầy đủ Họ và tên, Trường, Lớp, Email và Số điện thoại.', true); return; } submitBtn.disabled = true; submitBtn.innerHTML = '
Đang gửi...'; try { await fetch(form.action, { method: 'POST', body: new FormData(form), mode: 'no-cors' }); utils.showNotification('Gửi kết quả thành công!'); setTimeout(() => { utils.hideModal(elements.resultModal); submitBtn.disabled = false; submitBtn.innerHTML = 'Gửi kết quả'; }, 1500); } catch (error) { utils.showNotification('Đã xảy ra lỗi khi gửi kết quả. Vui lòng thử lại.', true); submitBtn.disabled = false; submitBtn.innerHTML = 'Gửi kết quả'; } } /* ============================================ * KHỞI CHẠY ỨNG DỤNG * ========================================== */ return { init: () => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setupEventListeners(); }); } else { setupEventListeners(); } }, utils: { handleImageError: utils.handleImageError, renderLatex: latexRenderer.renderLatex, renderContainer: latexRenderer.renderContainer, initMathFields: latexRenderer.initMathFields } }; })(); document.addEventListener('DOMContentLoaded', PhysicsQuizApp.init);

Không có nhận xét nào:

Đăng nhận xét

Nước mắm ngon
Mua nước mắm để miễn phí khóa học và tài liệu
Zalo Messenger

Hỗ trợ trực tuyến

×

Kết nối thành công! Bạn có thể nhắn tin với quản trị viên tại đây.