当前位置: 首页 > article >正文

使用 HTML + JavaScript 实现在线考试系统

在现代的在线教育平台中,在线考试系统是不可或缺的一部分。本文将通过一个完整的示例,演示如何使用 HTML、CSS 和 JavaScript 构建一个支持多种题型的在线考试系统。

效果演示

image-20250528213340576

image-20250528213416003

image-20250528213449248

项目概述

本项目主要包含以下核心功能:

  • 支持4种常见题型:单选题、多选题、判断题、填空题
  • 答题卡导航功能
  • 实时计时器
  • 自动评分与结果反馈

页面结构与样式设计

创建 HTML 结构
<div class="container"><!-- 考试内容 --><div class="exam-container"><!-- 标题和计时器 --><div class="header"><h2>在线考试系统</h2><div class="timer">剩余时间: <span id="time">30:00</span></div></div><!-- 题目 --><div id="subject"></div><!-- 导航按钮 --><div class="navigation"><button id="prev-btn" disabled>上一题</button><button id="next-btn">下一题</button><button id="submit-btn" class="submit-btn">提交试卷</button></div><!-- 结果 --><div id="result" class="result"><h2>考试结束</h2><p>你的得分是: <span class="score" id="final-score">0</span></p><p id="result-message"></p></div></div><!-- 答题卡 --><div class="answer-sheet"><h3>答题卡</h3><div class="answer-buttons" id="answer-buttons"></div></div>
</div>
设计 CSS 样式

整体布局

body {margin: 0;padding: 0;background-color: #f5f5f5;
}
.container {display: flex;max-width: 1200px;margin: 0 auto;padding: 20px;
}
.exam-container {flex: 3;background-color: white;padding: 30px;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);margin-right: 20px;
}
.answer-sheet {flex: 1;background-color: white;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);height: fit-content;position: sticky;top: 20px;
}

题目区域样式

.header {display: flex;justify-content: space-between;margin-bottom: 20px;padding-bottom: 10px;border-bottom: 1px solid #eee;
}
.timer {font-weight: bold;color: #e74c3c;
}
.question {margin-bottom: 20px;padding: 15px;background-color: #f9f9f9;border-radius: 5px;
}
.question h3 {margin-top: 0;color: #2c3e50;
}
.question-type {display: inline-block;padding: 2px 8px;background-color: #3498db;color: white;border-radius: 4px;font-size: 12px;margin-left: 10px;
}
.options {margin-left: 20px;
}
.option {margin: 10px 0;padding: 8px;cursor: pointer;border-radius: 4px;
}
.option:hover {background-color: #eee;
}
.option.selected {background-color: #3498db;color: white;
}
.option.multi-selected {background-color: #9b59b6;color: white;
}
.true-false-options {display: flex;gap: 20px;
}
.true-false-option {padding: 10px 20px;border: 1px solid #ddd;border-radius: 5px;cursor: pointer;
}
.true-false-option.selected {background-color: #3498db;color: white;border-color: #3498db;
}
.fill-blank-input {width: 100%;padding: 8px;margin-top: 10px;border: 1px solid #ddd;border-radius: 4px;font-size: 16px;
}
.navigation {display: flex;justify-content: space-between;margin-top: 30px;
}
button {padding: 10px 20px;background-color: #3498db;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;
}
button:hover {background-color: #2980b9;
}
button:disabled {background-color: #95a5a6;cursor: not-allowed;
}
.submit-btn {background-color: #2ecc71;
}
.submit-btn:hover {background-color: #27ae60;
}
.result {display: none;text-align: center;padding: 20px;
}
.score {font-size: 24px;font-weight: bold;color: #2ecc71;
}

答题卡样式

.answer-sheet h3 {margin-top: 0;padding-bottom: 10px;border-bottom: 1px solid #eee;text-align: center;
}
.answer-buttons {display: grid;grid-template-columns: repeat(5, 1fr);gap: 10px;
}
.answer-btn {width: 100%;aspect-ratio: 1;border: 1px solid #ddd;border-radius: 5px;display: flex;align-items: center;justify-content: center;cursor: pointer;font-weight: bold;background-color: white;
}
.answer-btn:hover {background-color: #f0f0f0;
}
.answer-btn.current {border: 2px solid #3498db;color: #3498db;
}
.answer-btn.answered {background-color: #3498db;color: white;border-color: #3498db;
}
.answer-btn.unanswered {background-color: #f1f1f1;color: #999;
}

核心功能实现

定义基础数据
// 题型常量
const QUESTION_TYPES = {SINGLE_CHOICE: 'single-choice',MULTI_CHOICE: 'multi-choice',TRUE_FALSE: 'true-false',FILL_BLANK: 'fill-blank'
};// 考试数据
const examData = {title: "JavaScript综合测试",timeLimit: 30 * 60, // 30分钟,以秒为单位questions: [{type: QUESTION_TYPES.SINGLE_CHOICE,question: "JavaScript是什么类型的语言?",options: ["编译型", "解释型", "混合型", "标记型"],answer: 1,score: 10},{type: QUESTION_TYPES.MULTI_CHOICE,question: "以下哪些是JavaScript的数据类型?(多选)",options: ["String", "Boolean", "Number", "Float", "Object"],answer: [0, 1, 2, 4],score: 15},// ...]
};
生成答题卡
function createAnswerSheet() {answerButtonsContainer.innerHTML = '';examData.questions.forEach((_, index) => {const btn = document.createElement('button');btn.className = 'answer-btn unanswered';btn.textContent = index + 1;btn.onclick = () => jumpToQuestion(index);answerButtonsContainer.appendChild(btn);});
}
渲染不同题型
function showQuestion() {const question = examData.questions[currentQuestion];const typeLabel = getTypeLabel(question.type);let html = `<div class="question">
<h3>题目 ${currentQuestion + 1}/${examData.questions.length}: ${question.question}
<span class="question-type">${typeLabel}</span>
</h3>
<div class="options">`;// 根据题型生成不同的HTMLswitch(question.type) {case QUESTION_TYPES.SINGLE_CHOICE:html += generateSingleChoiceHTML(question);break;case QUESTION_TYPES.MULTI_CHOICE:html += generateMultiChoiceHTML(question);break;case QUESTION_TYPES.TRUE_FALSE:html += generateTrueFalseHTML(question);break;case QUESTION_TYPES.FILL_BLANK:html += generateFillBlankHTML(question);break;}html += `</div></div>`;subjectContainer.innerHTML = html;// 更新导航按钮状态prevBtn.disabled = currentQuestion === 0;nextBtn.disabled = currentQuestion === examData.questions.length - 1;// 更新答题卡updateAnswerSheet();
}
跳转到指定题目
function jumpToQuestion(index) {currentQuestion = index;showQuestion();
}
更新答题卡状态
function updateAnswerSheet() {const buttons = answerButtonsContainer.querySelectorAll('.answer-btn');buttons.forEach((btn, index) => {btn.classList.remove('current', 'answered', 'unanswered');if (index === currentQuestion) {btn.classList.add('current');}if (userAnswers[index] === null || userAnswers[index] === '' || (Array.isArray(userAnswers[index]) && userAnswers[index].length === 0) ) {btn.classList.add('unanswered');} else {btn.classList.add('answered');}});
}
监听做题事件
事件名事件
选择单选题选项selectSingleChoice
选择多选题选项toggleMultiChoice
选择判断题选项selectTrueFalse
更新填空题答案updateFillBlankAnswer
提交试卷
function submitExam() {clearInterval(timer);// 计算分数let score = 0;examData.questions.forEach((question, index) => {const userAnswer = userAnswers[index];let isCorrect = false;switch(question.type) {case QUESTION_TYPES.SINGLE_CHOICE:isCorrect = userAnswer === question.answer;break;case QUESTION_TYPES.MULTI_CHOICE:if (Array.isArray(userAnswer)) {// 检查答案数组是否完全相同const userSorted = [...userAnswer].sort();const answerSorted = [...question.answer].sort();isCorrect = JSON.stringify(userSorted) === JSON.stringify(answerSorted);}break;case QUESTION_TYPES.TRUE_FALSE:isCorrect = userAnswer === question.answer;break;case QUESTION_TYPES.FILL_BLANK:isCorrect = userAnswer && userAnswer.toString().toLowerCase() === question.answer.toLowerCase();break;}if (isCorrect) {score += question.score;}});// 显示结果document.getElementById('subject').style.display = 'none';document.querySelector('.navigation').style.display = 'none';resultDiv.style.display = 'block';finalScore.textContent = score;// 根据分数显示不同消息const totalScore = examData.questions.reduce((sum, q) => sum + q.score, 0);const percentage = (score / totalScore) * 100;if (percentage >= 80) {resultMessage.textContent = "优秀!你掌握了大部分知识点。";} else if (percentage >= 60) {resultMessage.textContent = "良好!继续努力,你可以做得更好。";} else if (percentage >= 40) {resultMessage.textContent = "及格!建议复习相关知识点。";} else {resultMessage.textContent = "不及格!需要加强学习。";}
}

扩展建议

  • 防切屏功能
  • 倒计时功能优化
  • 实现错题回顾功能
  • 添加用户登录与成绩保存功能
  • 集成后端进行数据持久化

完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>在线考试系统 - 多题型版</title><style>body {margin: 0;padding: 0;background-color: #f5f5f5;}.container {display: flex;max-width: 1200px;margin: 0 auto;padding: 20px;}.exam-container {flex: 3;background-color: white;padding: 30px;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);margin-right: 20px;}.answer-sheet {flex: 1;background-color: white;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);height: fit-content;position: sticky;top: 20px;}.header {display: flex;justify-content: space-between;margin-bottom: 20px;padding-bottom: 10px;border-bottom: 1px solid #eee;}.timer {font-weight: bold;color: #e74c3c;}.question {margin-bottom: 20px;padding: 15px;background-color: #f9f9f9;border-radius: 5px;}.question h3 {margin-top: 0;color: #2c3e50;}.question-type {display: inline-block;padding: 2px 8px;background-color: #3498db;color: white;border-radius: 4px;font-size: 12px;margin-left: 10px;}.options {margin-left: 20px;}.option {margin: 10px 0;padding: 8px;cursor: pointer;border-radius: 4px;}.option:hover {background-color: #eee;}.option.selected {background-color: #3498db;color: white;}.option.multi-selected {background-color: #9b59b6;color: white;}.true-false-options {display: flex;gap: 20px;}.true-false-option {padding: 10px 20px;border: 1px solid #ddd;border-radius: 5px;cursor: pointer;}.true-false-option.selected {background-color: #3498db;color: white;border-color: #3498db;}.fill-blank-input {width: 100%;padding: 8px;margin-top: 10px;border: 1px solid #ddd;border-radius: 4px;font-size: 16px;}.navigation {display: flex;justify-content: space-between;margin-top: 30px;}button {padding: 10px 20px;background-color: #3498db;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;}button:hover {background-color: #2980b9;}button:disabled {background-color: #95a5a6;cursor: not-allowed;}.submit-btn {background-color: #2ecc71;}.submit-btn:hover {background-color: #27ae60;}.result {display: none;text-align: center;padding: 20px;}.score {font-size: 24px;font-weight: bold;color: #2ecc71;}.answer-sheet h3 {margin-top: 0;padding-bottom: 10px;border-bottom: 1px solid #eee;text-align: center;}.answer-buttons {display: grid;grid-template-columns: repeat(5, 1fr);gap: 10px;}.answer-btn {width: 100%;aspect-ratio: 1;border: 1px solid #ddd;border-radius: 5px;display: flex;align-items: center;justify-content: center;cursor: pointer;font-weight: bold;background-color: white;}.answer-btn:hover {background-color: #f0f0f0;}.answer-btn.current {border: 2px solid #3498db;color: #3498db;}.answer-btn.answered {background-color: #3498db;color: white;border-color: #3498db;}.answer-btn.unanswered {background-color: #f1f1f1;color: #999;}</style>
</head>
<body>
<div class="container"><!-- 考试内容 --><div class="exam-container"><!-- 标题和计时器 --><div class="header"><h2>在线考试系统</h2><div class="timer">剩余时间: <span id="time">30:00</span></div></div><!-- 题目 --><div id="subject"></div><!-- 导航按钮 --><div class="navigation"><button id="prev-btn" disabled>上一题</button><button id="next-btn">下一题</button><button id="submit-btn" class="submit-btn">提交试卷</button></div><!-- 结果 --><div id="result" class="result"><h2>考试结束</h2><p>你的得分是: <span class="score" id="final-score">0</span></p><p id="result-message"></p></div></div><!-- 答题卡 --><div class="answer-sheet"><h3>答题卡</h3><div class="answer-buttons" id="answer-buttons"></div></div>
</div><script>// 题型常量const QUESTION_TYPES = {SINGLE_CHOICE: 'single-choice',MULTI_CHOICE: 'multi-choice',TRUE_FALSE: 'true-false',FILL_BLANK: 'fill-blank'};// 考试数据const examData = {title: "JavaScript综合测试",timeLimit: 30 * 60, // 30分钟,以秒为单位questions: [{type: QUESTION_TYPES.SINGLE_CHOICE,question: "JavaScript是什么类型的语言?",options: ["编译型", "解释型", "混合型", "标记型"],answer: 1,score: 10},{type: QUESTION_TYPES.MULTI_CHOICE,question: "以下哪些是JavaScript的数据类型?(多选)",options: ["String", "Boolean", "Number", "Float", "Object"],answer: [0, 1, 2, 4], // 多选的答案使用数组score: 15},{type: QUESTION_TYPES.TRUE_FALSE,question: "JavaScript和Java是同一种语言。",answer: false, // true或falsescore: 5},{type: QUESTION_TYPES.FILL_BLANK,question: "用于向数组末尾添加元素的方法是______。",answer: "push", // 填空题的答案score: 10},{type: QUESTION_TYPES.SINGLE_CHOICE,question: "哪个方法可以将字符串转换为整数?",options: ["parseInt()", "parseString()", "toInteger()", "stringToInt()"],answer: 0,score: 10},{type: QUESTION_TYPES.MULTI_CHOICE,question: "以下哪些是JavaScript的循环语句?(多选)",options: ["for", "while", "do...while", "repeat", "loop"],answer: [0, 1, 2],score: 15},{type: QUESTION_TYPES.TRUE_FALSE,question: "JavaScript中可以使用let和const声明变量。",answer: true,score: 5},{type: QUESTION_TYPES.FILL_BLANK,question: "用于检测变量类型的操作符是______。",answer: "typeof",score: 10}]};// 全局变量let currentQuestion = 0;let userAnswers = Array(examData.questions.length).fill(null);let timeLeft = examData.timeLimit;let timer;// DOM元素const subjectContainer = document.getElementById('subject');const prevBtn = document.getElementById('prev-btn');const nextBtn = document.getElementById('next-btn');const submitBtn = document.getElementById('submit-btn');const timeDisplay = document.getElementById('time');const resultDiv = document.getElementById('result');const finalScore = document.getElementById('final-score');const resultMessage = document.getElementById('result-message');const answerButtonsContainer = document.getElementById('answer-buttons');// 初始化考试function initExam() {createAnswerSheet();showQuestion();startTimer();}// 创建答题卡function createAnswerSheet() {answerButtonsContainer.innerHTML = '';examData.questions.forEach((_, index) => {const btn = document.createElement('button');btn.className = 'answer-btn unanswered';btn.textContent = index + 1;btn.onclick = () => jumpToQuestion(index);answerButtonsContainer.appendChild(btn);});}// 更新答题卡状态function updateAnswerSheet() {const buttons = answerButtonsContainer.querySelectorAll('.answer-btn');buttons.forEach((btn, index) => {btn.classList.remove('current', 'answered', 'unanswered');if (index === currentQuestion) {btn.classList.add('current');}if (userAnswers[index] === null || userAnswers[index] === '' || (Array.isArray(userAnswers[index]) && userAnswers[index].length === 0) ) {btn.classList.add('unanswered');} else {btn.classList.add('answered');}});}// 跳转到指定题目function jumpToQuestion(index) {currentQuestion = index;showQuestion();}// 显示当前题目function showQuestion() {const question = examData.questions[currentQuestion];const typeLabel = getTypeLabel(question.type);let html = `<div class="question"><h3>题目 ${currentQuestion + 1}/${examData.questions.length}: ${question.question}<span class="question-type">${typeLabel}</span></h3><div class="options">`;// 根据题型生成不同的HTMLswitch(question.type) {case QUESTION_TYPES.SINGLE_CHOICE:html += generateSingleChoiceHTML(question);break;case QUESTION_TYPES.MULTI_CHOICE:html += generateMultiChoiceHTML(question);break;case QUESTION_TYPES.TRUE_FALSE:html += generateTrueFalseHTML(question);break;case QUESTION_TYPES.FILL_BLANK:html += generateFillBlankHTML(question);break;}html += `</div></div>`;subjectContainer.innerHTML = html;// 更新导航按钮状态prevBtn.disabled = currentQuestion === 0;nextBtn.disabled = currentQuestion === examData.questions.length - 1;// 更新答题卡updateAnswerSheet();}// 获取题型标签function getTypeLabel(type) {switch(type) {case QUESTION_TYPES.SINGLE_CHOICE: return '单选题';case QUESTION_TYPES.MULTI_CHOICE: return '多选题';case QUESTION_TYPES.TRUE_FALSE: return '判断题';case QUESTION_TYPES.FILL_BLANK: return '填空题';default: return '';}}// 生成单选题function generateSingleChoiceHTML(question) {let html = '';question.options.forEach((option, index) => {const isSelected = userAnswers[currentQuestion] === index;html += `<div class="option ${isSelected ? 'selected' : ''}" onclick="selectSingleChoice(${index})">${String.fromCharCode(65 + index)}. ${option}</div>`;});return html;}// 生成多选题function generateMultiChoiceHTML(question) {let html = '';question.options.forEach((option, index) => {const isSelected = Array.isArray(userAnswers[currentQuestion]) && userAnswers[currentQuestion].includes(index);html += `<div class="option ${isSelected ? 'multi-selected' : ''}" onclick="toggleMultiChoice(${index})">${String.fromCharCode(65 + index)}. ${option}</div>`;});return html;}// 生成判断题function generateTrueFalseHTML(question) {const userAnswer = userAnswers[currentQuestion];return `<div class="true-false-options"><div class="true-false-option ${userAnswer === true ? 'selected' : ''}"onclick="selectTrueFalse(true)">正确</div><div class="true-false-option ${userAnswer === false ? 'selected' : ''}"onclick="selectTrueFalse(false)">错误</div></div>`;}// 生成填空题function generateFillBlankHTML(question) {const userAnswer = userAnswers[currentQuestion] || '';return `<input type="text" class="fill-blank-input"value="${userAnswer}"oninput="updateFillBlankAnswer(this.value)"placeholder="请输入答案">`;}// 选择单选题选项function selectSingleChoice(optionIndex) {userAnswers[currentQuestion] = optionIndex;showQuestion();}// 选择多选题选项function toggleMultiChoice(optionIndex) {if (!Array.isArray(userAnswers[currentQuestion])) {userAnswers[currentQuestion] = [];}const index = userAnswers[currentQuestion].indexOf(optionIndex);if (index === -1) {userAnswers[currentQuestion].push(optionIndex);} else {userAnswers[currentQuestion].splice(index, 1);}showQuestion();}// 选择判断题选项function selectTrueFalse(answer) {userAnswers[currentQuestion] = answer;showQuestion();}// 更新填空题答案function updateFillBlankAnswer(answer) {userAnswers[currentQuestion] = answer.trim();updateAnswerSheet();}// 上一题function prevQuestion() {if (currentQuestion > 0) {currentQuestion--;showQuestion();}}// 下一题function nextQuestion() {if (currentQuestion < examData.questions.length - 1) {currentQuestion++;showQuestion();}}// 开始计时器function startTimer() {updateTimerDisplay();timer = setInterval(() => {timeLeft--;updateTimerDisplay();if (timeLeft <= 0) {clearInterval(timer);submitExam();}}, 1000);}// 更新计时器显示function updateTimerDisplay() {const minutes = Math.floor(timeLeft / 60);const seconds = timeLeft % 60;timeDisplay.textContent = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;}// 提交试卷function submitExam() {clearInterval(timer);// 计算分数let score = 0;examData.questions.forEach((question, index) => {const userAnswer = userAnswers[index];let isCorrect = false;switch(question.type) {case QUESTION_TYPES.SINGLE_CHOICE:isCorrect = userAnswer === question.answer;break;case QUESTION_TYPES.MULTI_CHOICE:if (Array.isArray(userAnswer)) {// 检查答案数组是否完全相同const userSorted = [...userAnswer].sort();const answerSorted = [...question.answer].sort();isCorrect = JSON.stringify(userSorted) === JSON.stringify(answerSorted);}break;case QUESTION_TYPES.TRUE_FALSE:isCorrect = userAnswer === question.answer;break;case QUESTION_TYPES.FILL_BLANK:isCorrect = userAnswer && userAnswer.toString().toLowerCase() === question.answer.toLowerCase();break;}if (isCorrect) {score += question.score;}});// 显示结果document.getElementById('subject').style.display = 'none';document.querySelector('.navigation').style.display = 'none';resultDiv.style.display = 'block';finalScore.textContent = score;// 根据分数显示不同消息const totalScore = examData.questions.reduce((sum, q) => sum + q.score, 0);const percentage = (score / totalScore) * 100;if (percentage >= 80) {resultMessage.textContent = "优秀!你掌握了大部分知识点。";} else if (percentage >= 60) {resultMessage.textContent = "良好!继续努力,你可以做得更好。";} else if (percentage >= 40) {resultMessage.textContent = "及格!建议复习相关知识点。";} else {resultMessage.textContent = "不及格!需要加强学习。";}}// 事件监听prevBtn.addEventListener('click', prevQuestion);nextBtn.addEventListener('click', nextQuestion);submitBtn.addEventListener('click', () => {if (confirm("确定要提交试卷吗?提交后将无法修改答案。")) {submitExam();}});// 开始考试initExam();
</script>
</body>
</html>
http://www.lryc.cn/news/2395259.html

相关文章:

  • 谷歌工作自动化——仙盟大衍灵机——仙盟创梦IDE
  • 嵌入式(C语言篇)Day13
  • Oracle 的V$LOCK 视图详解
  • 秒杀系统—1.架构设计和方案简介
  • 基于FashionMnist数据集的自监督学习(生成式自监督学习AE算法)
  • 从监控到告警:Prometheus+Grafana+Alertmanager+告警通知服务全链路落地实践
  • AUTOSAR图解==>AUTOSAR_EXP_AIADASAndVMC
  • WPF【09】WPF基础入门 (三层架构与MVC架构)
  • macOS 风格番茄计时器:设计与实现详解
  • 中文NLP with fastai - Fastai Part4
  • oracle goldengate实现远程抽取postgresql 到 postgresql的实时同步【绝对无坑版,亲测流程验证】
  • 【MYSQL】索引篇(一)
  • ISCC-2025-web-wp
  • 鸿蒙分辨率
  • @Docker Compose 部署 Pushgateway
  • 我们来学mysql -- 从库重启,是否同步主库数据
  • King3399(ubuntu文件系统)iic(i2c)功能测试
  • 德思特新闻 | 德思特与es:saar正式建立合作伙伴关系
  • 基于原生JavaScript前端和 Flask 后端的Todo 应用
  • 一些Dify聊天系统组件流程图架构图
  • jq处理日志数据
  • Matlab程序设计基础
  • MIT 6.S081 2020 Lab6 Copy-on-Write Fork for xv6 个人全流程
  • 第304个Vulnhub靶场演练攻略:digital world.local:FALL
  • Unity 模拟高度尺系统开发详解——实现拖动、范围限制、碰撞吸附与本地坐标轴选择
  • 万字详解RTR RTSP SDP RTCP
  • 云服务器如何自动更新系统并保持安全?
  • 训练中常见的运动强度分类
  • java 递归地复制文件夹及其所有子文件夹和文件
  • [paddle]paddle2onnx无法转换Paddle3.0.0的json格式paddle inference模型