Bài Kiểm Tra Online
1. Cấu trúc Google Sheets
✅ Tab “Kết quả”:
• Cột A: Tên học sinh.
• Cột B: Điểm số.
• Cột C: Thời gian hoàn thành.
• Cột D: Đáp án chi tiết.
✅ Tab “Thống kê”: (Tự động cập nhật)
• Cột A: Tổng số học sinh.
• Cột B: Điểm trung bình.
• Cột C: Điểm cao nhất.
• Cột D: Điểm thấp nhất.
⸻
🔧 2. Google Apps Script: Xử lý lưu kết quả tự động và thống kê
1. Trong Google Sheets → Extensions → Apps Script, thêm mã sau:
// Nhận kết quả từ web và lưu vào Google Sheets
function doPost(e) {
const sheet = SpreadsheetApp.getActiveSpreadsheet();
const resultSheet = sheet.getSheetByName("Kết quả");
const statSheet = sheet.getSheetByName("Thống kê") || sheet.insertSheet("Thống kê");
const data = JSON.parse(e.postData.contents);
const studentName = data.studentName;
const score = parseFloat(data.score);
const timestamp = new Date().toLocaleString();
const details = data.details;
// Kiểm tra nếu học sinh đã nộp bài
const existingEntries = resultSheet.getDataRange().getValues();
const existingStudent = existingEntries.find(row => row[0] === studentName);
if (existingStudent) {
return ContentService
.createTextOutput(JSON.stringify({status: "duplicate", message: "Học sinh này đã nộp bài."}))
.setMimeType(ContentService.MimeType.JSON);
}
resultSheet.appendRow([studentName, score, timestamp, details]);
updateStatistics(resultSheet, statSheet);
return ContentService
.createTextOutput(JSON.stringify({status: "success"}))
.setMimeType(ContentService.MimeType.JSON);
}
// Hàm cập nhật thống kê
function updateStatistics(resultSheet, statSheet) {
const data = resultSheet.getDataRange().getValues().slice(1); // Bỏ dòng tiêu đề
const scores = data.map(row => row[1]).filter(score => !isNaN(score));
if (scores.length === 0) return;
const totalStudents = scores.length;
const averageScore = (scores.reduce((a, b) => a + b, 0) / totalStudents).toFixed(2);
const highestScore = Math.max(...scores);
const lowestScore = Math.min(...scores);
statSheet.clear();
statSheet.appendRow(["Tổng số học sinh", "Điểm trung bình", "Điểm cao nhất", "Điểm thấp nhất"]);
statSheet.appendRow([totalStudents, averageScore, highestScore, lowestScore]);
}
2. Lưu lại mã này và triển khai:
• File > Deploy > New Deployment.
• Chọn “Web app”.
• Đặt quyền “Anyone” (bất kỳ ai).
• Lấy URL của Web app này.
⸻
🔧 3. HTML + JavaScript: Phiên bản nâng cấp hoàn chỉnh
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: auto; padding: 20px; }
.question { margin-bottom: 20px; }
.submit-btn { background: #4CAF50; color: #fff; border: none; padding: 10px 20px; cursor: pointer; }
.submit-btn:hover { background: #45a049; }
.timer { font-size: 18px; margin-bottom: 20px; text-align: right; color: #ff0000; }
.result { display: none; margin-top: 20px; padding: 15px; background: #f0f0f0; }
.correct { color: green; }
.incorrect { color: red; }
</style>
<h2>Bài Kiểm Tra Online</h2>
<label for="studentName">Tên học sinh:</label>
<input type="text" id="studentName" placeholder="Nhập tên của bạn" required>
<div class="timer" id="timer">Thời gian còn lại: 00:00</div>
<form id="quizForm">
<div id="quizContainer"></div>
<button type="button" class="submit-btn" onclick="submitQuiz()">Nộp bài</button>
</form>
<div class="result" id="result">
<h3>Kết quả của bạn:</h3>
<div id="resultDetails"></div>
<p><strong>Điểm của bạn:</strong> <span id="score"></span>/100</p>
</div>
<script>
const sheetId = "ID_SHEET";
const apiKey = "YOUR_API_KEY";
const submitUrl = "URL_WEB_APP"; // URL của Web app đã triển khai từ Apps Script
let timeRemaining;
let timer;
document.addEventListener("DOMContentLoaded", loadQuiz);
async function loadQuiz() {
const settings = await fetchSettings();
const now = new Date();
const startTime = new Date(settings.startTime);
const endTime = new Date(settings.endTime);
const duration = settings.duration * 60;
if (now < startTime) {
alert("Bài kiểm tra chưa bắt đầu. Vui lòng quay lại sau.");
document.getElementById("quizForm").style.display = "none";
return;
}
if (now > endTime) {
alert("Bài kiểm tra đã kết thúc.");
document.getElementById("quizForm").style.display = "none";
return;
}
timeRemaining = Math.min(duration, (endTime - now) / 1000);
startTimer();
loadQuestions();
}
async function fetchSettings() {
const response = await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/Cài đặt?key=${apiKey}`);
const data = await response.json();
const settings = {};
data.values.forEach(([key, value]) => {
if (key === "Thời gian bắt đầu") settings.startTime = value.trim();
if (key === "Thời gian kết thúc") settings.endTime = value.trim();
if (key === "Thời gian làm bài") settings.duration = parseInt(value.trim());
});
return settings;
}
async function loadQuestions() {
const response = await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/Câu hỏi?key=${apiKey}`);
const data = await response.json();
const container = document.getElementById("quizContainer");
data.values.slice(1).forEach(([type, question, answer, ...options]) => {
const div = document.createElement("div");
div.className = "question";
div.setAttribute("data-answer", answer.trim());
div.innerHTML = `<strong>${question}</strong><br>`;
options.forEach(option => {
if (option) {
div.innerHTML += `<label><input type="radio" name="${question}" value="${option.trim()}"> ${option.trim()}</label><br>`;
}
});
container.appendChild(div);
});
}
function startTimer() {
timer = setInterval(() => {
timeRemaining--;
const minutes = Math.floor(timeRemaining / 60);
const seconds = timeRemaining % 60;
document.getElementById("timer").textContent = `Thời gian còn lại: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
if (timeRemaining <= 0) {
clearInterval(timer);
submitQuiz();
}
}, 1000);
}
async function submitQuiz() {
clearInterval(timer);
const studentName = document.getElementById("studentName").value.trim();
if (!studentName) {
alert("Vui lòng nhập tên của bạn.");
return;
}
const response = await fetch(submitUrl, {
method: "POST",
body: JSON.stringify({
studentName,
score: calculateScore(),
details: getAnswerDetails()
})
});
const result = await response.json();
if (result.status === "duplicate") {
alert(result.message);
return;
}
}
</script>