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>