好友
阅读权限25
听众
最后登录1970-1-1
|
本帖最后由 zhengzhenhui945 于 2025-5-5 14:33 编辑
HTML、CSS 和 JavaScript 混合编写的,仅20多KB
支持【单选题、多选题、填空、判断题、配图】
功能说明
1. 可以使用数字键盘(主键盘区和小键盘区)的 1 - 9 键选择答案。对于多选题,可连续按数字键选择多个选项。
2. 可以使用鼠标点选选项,按空格键提交答案。
3. 如果选错答案,正确答案会被框选并闪烁 3 秒后跳转到下一题。
4. 可以使用键盘上的上箭头键切换到上一题,下箭头键切换到下一题。
5. 可以通过类目筛选功能,选择特定类目的题目进行刷题。
6. 题目配图固定尺寸显示,点击图片可放大查看。
[HTML] 纯文本查看 复制代码 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
<script src="https://unpkg.com/xlsx/dist/xlsx.full.min.js"></script>
<title>题库刷题</title>
<style>
#drop-area {
border: 2px dashed #ccc;
border-radius: 20px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
/* 闪烁动画 */
.blink-border {
animation: blinkBorder 1s linear infinite;
}
@keyframes blinkBorder {
50% {
border-color: transparent;
}
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
border-radius: 8px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
/* 修改题目导航按钮样式,使其自动换行 */
#question-nav {
flex-wrap: wrap;
}
/* 框选正确答案的样式 */
.selected-correct {
border: 2px solid red;
border-radius: 4px;
padding: 2px;
}
/* 图片样式 */
.question-image {
max-width: 200px;
max-height: 200px;
margin-bottom: 10px;
cursor: pointer;
}
/* 放大图片模态框样式 */
#image-modal {
display: none;
position: fixed;
z-index: 2;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.9);
}
#modal-image {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
margin-top: 10%;
}
#close-modal {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="container mx-auto p-8">
<h1 class="text-3xl font-bold text-center mb-8">题库刷题</h1>
<div id="drop-area">
<p>将 .xlsx 文件拖到这里进行导入</p>
<input type="file" id="import-file" accept=".xlsx" style="display: none;">
</div>
<div class="flex justify-between mb-4">
<div class="flex space-x-2">
<button class="bg-blue-500 text-white py-2 px-4 rounded-md">导出题目</button>
<button class="bg-green-500 text-white py-2 px-4 rounded-md">添加题目</button>
<button class="bg-yellow-500 text-white py-2 px-4 rounded-md">说明</button>
</div>
<select id="category-filter" class="p-2 border border-gray-300 rounded-md">
<option value="">所有类目</option>
</select>
</div>
<!-- 修改题目导航按钮间距 -->
<div id="question-nav" class="flex justify-center space-x-4 gap-y-4 mb-4"></div>
<div id="question-container" class="bg-white p-8 rounded-lg shadow-md">
<p id="question-number" class="text-lg mb-2"></p>
<p id="question" class="text-lg mb-4"></p>
<img id="question-image" class="question-image" src="" alt="Question Image" style="display: none;">
<div id="options" class="space-y-2"></div>
<div class="flex space-x-4">
<button id="prev-question" class="bg-orange-500 text-white py-2 px-4 rounded-md mt-4">上一题</button>
<button id="submit" class="bg-blue-500 text-white py-2 px-4 rounded-md mt-4">提交答案</button>
<button id="next-question" class="bg-green-500 text-white py-2 px-4 rounded-md mt-4">下一题</button>
<button id="random-question" class="bg-pink-500 text-white py-2 px-4 rounded-md mt-4">随机一题</button>
</div>
</div>
<div id="result" class="mt-4 text-lg"></div>
<div id="stats-display" class="mt-4 text-lg">
正确题目数量: <span id="correct-count">0</span>
错误题目数量: <span id="wrong-count">0</span>
</div>
<div id="wrong-questions" class="mt-4 hidden">
<h2 class="text-xl font-bold mb-2">错误题目列表</h2>
<ul id="wrong-questions-list"></ul>
</div>
<div id="edit-container" class="bg-white p-8 rounded-lg shadow-md mt-8 hidden">
<h2 class="text-xl font-bold mb-4">编辑题目</h2>
<input type="text" id="edit-question" placeholder="题目内容" class="w-full p-2 border border-gray-300 rounded-md mb-2">
<input type="text" id="edit-image" placeholder="图片路径" class="w-full p-2 border border-gray-300 rounded-md mb-2">
<div id="edit-options" class="space-y-2"></div>
<button class="bg-green-500 text-white py-2 px-4 rounded-md">添加选项</button>
<div id="correct-answer-select" class="mt-4 hidden">
<label for="correct-answer" class="block text-sm font-medium text-gray-700">选择正确答案</label>
<select id="correct-answer" multiple class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"></select>
</div>
<div class="mt-4">
<label for="question-type" class="block text-sm font-medium text-gray-700">题目类型</label>
<select id="question-type" class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="single">单选题</option>
<option value="multiple">多选题</option>
</select>
</div>
<div class="mt-4">
<label for="question-category" class="block text-sm font-medium text-gray-700">题目类目</label>
<input type="text" id="question-category" placeholder="请输入类目" class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded-md ml-2">保存题目</button>
<button class="bg-red-500 text-white py-2 px-4 rounded-md ml-2">取消编辑</button>
</div>
<!-- 说明模态框 -->
<div id="instructions-modal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>功能说明</h2>
<p>1. 可以使用数字键盘(主键盘区和小键盘区)的 1 - 9 键选择答案。对于多选题,可连续按数字键选择多个选项。</p>
<p>2. 可以使用鼠标点选选项,按空格键提交答案。</p>
<p>3. 如果选错答案,正确答案会被框选并闪烁 3 秒后跳转到下一题。</p>
<p>4. 可以使用键盘上的上箭头键切换到上一题,下箭头键切换到下一题。</p>
<p>5. 可以通过类目筛选功能,选择特定类目的题目进行刷题。</p>
<p>6. 题目配图固定尺寸显示,点击图片可放大查看。</p>
</div>
</div>
<!-- 放大图片模态框 -->
<div id="image-modal" class="modal">
<span id="close-modal">×</span>
<img id="modal-image" class="modal-content" src="">
</div>
</div>
<script>
let questions = [
{
question: "以下哪种是编程语言?",
options: ["HTML", "CSS", "JavaScript", "Photoshop"],
answer: ["JavaScript"],
type: "single",
category: "编程",
image: ""
},
{
question: "以下哪些是数据库管理系统?",
options: ["Word", "Excel", "MySQL", "PowerPoint", "Oracle"],
answer: ["MySQL", "Oracle"],
type: "multiple",
category: "数据库",
image: ""
}
];
let currentQuestion = 0;
let isEditing = false;
let editingIndex = null;
let correctCount = 0;
let wrongCount = 0;
let wrongQuestionList = [];
let filteredQuestions = questions;
function loadQuestion() {
const questionNumberElement = document.getElementById('question-number');
const questionElement = document.getElementById('question');
const optionsElement = document.getElementById('options');
const imageElement = document.getElementById('question-image');
const current = filteredQuestions[currentQuestion];
questionNumberElement.textContent = `第 ${currentQuestion + 1} 题`;
questionElement.textContent = current.question;
optionsElement.innerHTML = '';
if (current.image) {
imageElement.src = current.image;
imageElement.style.display = 'block';
} else {
imageElement.style.display = 'none';
}
if (current.type === 'single') {
current.options.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.innerHTML = `
<input type="radio" id="option-${index}" name="answer" value="${option}">
<label for="option-${index}">${index + 1}. ${option}</label>
`;
optionsElement.appendChild(optionElement);
});
} else {
current.options.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.innerHTML = `
<input type="checkbox" id="option-${index}" name="answer" value="${option}">
<label for="option-${index}">${index + 1}. ${option}</label>
`;
optionsElement.appendChild(optionElement);
});
}
updateQuestionNav();
}
function checkAnswer() {
const current = filteredQuestions[currentQuestion];
let selectedOptions;
if (current.type === 'single') {
selectedOptions = [document.querySelector('input[name="answer"]:checked')?.value];
} else {
selectedOptions = Array.from(document.querySelectorAll('input[name="answer"]:checked')).map(input => input.value);
}
const resultElement = document.getElementById('result');
if (selectedOptions.length === 0) {
resultElement.textContent = "请选择一个答案!";
resultElement.classList.add('text-red-500');
return;
}
const isCorrect = selectedOptions.sort().toString() === current.answer.sort().toString();
if (isCorrect) {
resultElement.textContent = "回答正确!";
resultElement.classList.add('text-green-500');
resultElement.classList.remove('text-red-500');
correctCount++;
document.getElementById('correct-count').textContent = correctCount;
goToNextQuestion();
} else {
resultElement.textContent = `回答错误,正确答案是:${current.answer.join(', ')}`;
resultElement.classList.add('text-red-500');
resultElement.classList.remove('text-green-500');
wrongCount++;
document.getElementById('wrong-count').textContent = wrongCount;
wrongQuestionList.push({
question: current.question,
userAnswer: selectedOptions.join(', '),
correctAnswer: current.answer.join(', ')
});
current.answer.forEach(answer => {
const answerIndex = current.options.indexOf(answer);
const answerLabel = document.querySelector(`label[for="option-${answerIndex}"]`);
answerLabel.classList.add('selected-correct', 'blink-border');
setTimeout(() => {
answerLabel.classList.remove('selected-correct', 'blink-border');
}, 3000);
});
setTimeout(() => {
goToNextQuestion();
}, 3000);
}
}
function importQuestions() {
const fileInput = document.getElementById('import-file');
const file = fileInput.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
try {
questions = jsonData.map(item => {
const options = [];
let i = 1;
while (item[`option${i}`]) {
options.push(item[`option${i}`]);
i++;
}
const answer = item.answer.split(',');
return {
question: item.question,
options: options,
answer: answer,
type: item.type,
category: item.category,
image: item.image || ""
};
});
filteredQuestions = questions;
currentQuestion = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestionList = [];
document.getElementById('correct-count').textContent = correctCount;
document.getElementById('wrong-count').textContent = wrongCount;
document.getElementById('wrong-questions').classList.add('hidden');
document.getElementById('wrong-questions-list').innerHTML = '';
loadQuestion();
document.getElementById('question-container').style.display = 'block';
document.getElementById('result').textContent = '';
populateCategoryFilter();
} catch (error) {
alert('导入文件格式错误,请确保文件结构正确。');
}
};
reader.readAsArrayBuffer(file);
}
}
function exportQuestions() {
const exportData = [];
questions.forEach(question => {
const row = {
question: question.question,
answer: question.answer.join(','),
type: question.type,
category: question.category,
image: question.image
};
question.options.forEach((option, index) => {
row[`option${index + 1}`] = option;
});
exportData.push(row);
});
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Questions');
XLSX.writeFile(wb, 'questions.xlsx');
}
function addQuestion() {
isEditing = false;
editingIndex = null;
document.getElementById('edit-question').value = '';
document.getElementById('edit-image').value = '';
document.getElementById('edit-options').innerHTML = '';
document.getElementById('correct-answer-select').classList.add('hidden');
document.getElementById('edit-container').classList.remove('hidden');
}
function addOption() {
const optionsElement = document.getElementById('edit-options');
const optionInput = document.createElement('input');
optionInput.type = 'text';
optionInput.placeholder = '选项内容';
optionInput.classList.add('w-full', 'p-2', 'border', 'border-gray-300', 'rounded-md', 'mb-2');
optionsElement.appendChild(optionInput);
const optionCount = optionsElement.children.length;
if (optionCount > 0) {
document.getElementById('correct-answer-select').classList.remove('hidden');
}
const select = document.getElementById('correct-answer');
const option = document.createElement('option');
option.value = optionCount - 1;
option.textContent = `选项 ${optionCount}`;
select.appendChild(option);
}
function saveQuestion() {
const questionInput = document.getElementById('edit-question');
const imageInput = document.getElementById('edit-image');
const optionsInputs = document.querySelectorAll('#edit-options input');
const question = questionInput.value;
const image = imageInput.value;
const options = [];
optionsInputs.forEach(input => {
if (input.value) {
options.push(input.value);
}
});
const correctAnswerIndices = Array.from(document.getElementById('correct-answer').selectedOptions).map(option => option.value);
const answer = correctAnswerIndices.map(index => options[index]);
const questionType = document.getElementById('question-type').value;
const questionCategory = document.getElementById('question-category').value;
if (question && options.length > 0 && answer.length > 0 && questionCategory) {
const newQuestion = {
question: question,
options: options,
answer: answer,
type: questionType,
category: questionCategory,
image: image
};
if (isEditing) {
questions[editingIndex] = newQuestion;
} else {
questions.push(newQuestion);
}
filteredQuestions = questions;
currentQuestion = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestionList = [];
document.getElementById('correct-count').textContent = correctCount;
document.getElementById('wrong-count').textContent = wrongCount;
document.getElementById('wrong-questions').classList.add('hidden');
document.getElementById('wrong-questions-list').innerHTML = '';
loadQuestion();
document.getElementById('edit-container').classList.add('hidden');
populateCategoryFilter();
} else {
alert('请输入题目、至少一个选项、选择正确答案并填写题目类目。');
}
}
function cancelEdit() {
document.getElementById('edit-container').classList.add('hidden');
}
function updateQuestionNav() {
const nav = document.getElementById('question-nav');
nav.innerHTML = '';
filteredQuestions.forEach((_, index) => {
const button = document.createElement('button');
button.textContent = index + 1;
button.classList.add('bg-gray-300', 'text-black', 'py-2', 'px-4', 'rounded-md');
if (index === currentQuestion) {
button.classList.add('bg-blue-500', 'text-white');
}
button.addEventListener('click', () => {
currentQuestion = index;
loadQuestion();
document.getElementById('result').textContent = '';
});
nav.appendChild(button);
});
}
function goToPrevQuestion() {
if (currentQuestion > 0) {
currentQuestion--;
loadQuestion();
document.getElementById('result').textContent = '';
}
}
function goToNextQuestion() {
if (currentQuestion < filteredQuestions.length - 1) {
currentQuestion++;
loadQuestion();
document.getElementById('result').textContent = '';
} else {
document.getElementById('result').textContent = `所有题目已完成!正确题目数量: ${correctCount},错误题目数量: ${wrongCount}`;
document.getElementById('question-container').style.display = 'none';
if (wrongCount > 0) {
const wrongQuestionsList = document.getElementById('wrong-questions-list');
wrongQuestionsList.innerHTML = '';
wrongQuestionList.forEach(item => {
const listItem = document.createElement('li');
listItem.innerHTML = `
<p><strong>题目:</strong> ${item.question}</p>
<p><strong>你的答案:</strong> ${item.userAnswer}</p>
<p><strong>正确答案:</strong> <span class="text-red-500">${item.correctAnswer}</span></p>
`;
wrongQuestionsList.appendChild(listItem);
});
document.getElementById('wrong-questions').classList.remove('hidden');
}
}
}
function goToRandomQuestion() {
currentQuestion = Math.floor(Math.random() * filteredQuestions.length);
loadQuestion();
document.getElementById('result').textContent = '';
}
const dropArea = document.getElementById('drop-area');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropArea.style.borderColor = 'blue';
}
function unhighlight() {
dropArea.style.borderColor = '#ccc';
}
dropArea.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
const fileInput = document.getElementById('import-file');
fileInput.files = files;
importQuestions();
}
}
// 监听键盘事件
window.addEventListener('keydown', function (event) {
if (event.code === 'Space') {
checkAnswer();
} else if (/^(Digit|Numpad)[1-9]$/.test(event.code)) {
const optionIndex = parseInt(event.code.slice(-1)) - 1;
const option = document.getElementById(`option-${optionIndex}`);
if (option) {
if (filteredQuestions[currentQuestion].type === 'single') {
option.checked = true;
checkAnswer();
} else {
option.checked = !option.checked;
}
}
} else if (event.code === 'ArrowUp') {
goToPrevQuestion();
} else if (event.code === 'ArrowDown') {
goToNextQuestion();
}
});
// 显示说明模态框
function showInstructions() {
const modal = document.getElementById('instructions-modal');
modal.style.display = "block";
}
// 关闭说明模态框
const instructionsCloseBtn = document.querySelector('#instructions-modal .close');
instructionsCloseBtn.addEventListener('click', function () {
const modal = document.getElementById('instructions-modal');
modal.style.display = "none";
});
// 点击说明模态框外部关闭
window.addEventListener('click', function (event) {
const modal = document.getElementById('instructions-modal');
if (event.target == modal) {
modal.style.display = "none";
}
});
// 点击图片放大
const questionImage = document.getElementById('question-image');
const imageModal = document.getElementById('image-modal');
const modalImage = document.getElementById('modal-image');
const closeModal = document.getElementById('close-modal');
questionImage.addEventListener('click', function () {
if (questionImage.style.display!== 'none') {
imageModal.style.display = "block";
modalImage.src = questionImage.src;
}
});
closeModal.addEventListener('click', function () {
imageModal.style.display = "none";
});
// 点击放大图片模态框外部关闭
window.addEventListener('click', function (event) {
if (event.target == imageModal) {
imageModal.style.display = "none";
}
});
function populateCategoryFilter() {
const categoryFilter = document.getElementById('category-filter');
categoryFilter.innerHTML = '<option value="">所有类目</option>';
const categories = [...new Set(questions.map(question => question.category))];
categories.forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categoryFilter.appendChild(option);
});
}
function filterQuestionsByCategory() {
const selectedCategory = document.getElementById('category-filter').value;
if (selectedCategory === '') {
filteredQuestions = questions;
} else {
filteredQuestions = questions.filter(question => question.category === selectedCategory);
}
currentQuestion = 0;
loadQuestion();
}
populateCategoryFilter();
loadQuestion();
</script>
</body>
</html>
=======================================
V3版本的代码
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
<script src="https://unpkg.com/xlsx/dist/xlsx.full.min.js"></script>
<title>题库刷题</title>
<style>
#drop-area {
border: 2px dashed #ccc;
border-radius: 20px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
/* 闪烁动画 */
.blink-border {
animation: blinkBorder 1s linear infinite;
}
@keyframes blinkBorder {
50% {
border-color: transparent;
}
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
border-radius: 8px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
/* 修改题目导航按钮样式,使其自动换行 */
#question-nav {
flex-wrap: wrap;
}
/* 框选正确答案的样式 */
.selected-correct {
border: 2px solid red;
border-radius: 4px;
padding: 2px;
}
/* 图片样式 */
.question-image {
max-width: 200px;
max-height: 200px;
margin-bottom: 10px;
cursor: pointer;
}
/* 放大图片模态框样式 */
#image-modal {
display: none;
position: fixed;
z-index: 2;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.9);
}
#modal-image {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
margin-top: 10%;
}
#close-modal {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="container mx-auto p-8">
<h1 class="text-3xl font-bold text-center mb-8">题库刷题-52pojie</h1>
<div id="drop-area">
<p>将 .xlsx 文件拖到这里进行导入</p>
<input type="file" id="import-file" accept=".xlsx" style="display: none;">
</div>
<div class="flex justify-between mb-4">
<div class="flex space-x-2">
<button class="bg-blue-500 text-white py-2 px-4 rounded-md">导出题目</button>
<button class="bg-green-500 text-white py-2 px-4 rounded-md">添加题目</button>
<button class="bg-purple-500 text-white py-2 px-4 rounded-md">重头答题</button>
<button class="bg-yellow-500 text-white py-2 px-4 rounded-md">说明</button>
</div>
<select id="category-filter" class="p-2 border border-gray-300 rounded-md">
<option value="">所有类目</option>
</select>
</div>
<!-- 修改题目导航按钮间距 -->
<div id="question-nav" class="flex justify-center space-x-4 gap-y-4 mb-4"></div>
<div id="question-container" class="bg-white p-8 rounded-lg shadow-md">
<p id="question-number" class="text-lg mb-2"></p>
<p id="question" class="text-lg mb-4"></p>
<img id="question-image" class="question-image" src="" alt="Question Image" style="display: none;">
<div id="options" class="space-y-2"></div>
<div class="flex space-x-4">
<button id="prev-question" class="bg-orange-500 text-white py-2 px-4 rounded-md mt-4">上一题</button>
<button id="submit" class="bg-blue-500 text-white py-2 px-4 rounded-md mt-4">提交答案</button>
<button id="next-question" class="bg-green-500 text-white py-2 px-4 rounded-md mt-4">下一题</button>
<button id="random-question" class="bg-pink-500 text-white py-2 px-4 rounded-md mt-4">随机一题</button>
<button id="end-exam" class="bg-red-500 text-white py-2 px-4 rounded-md mt-4">结束答题</button>
</div>
</div>
<div id="result" class="mt-4 text-lg"></div>
<div id="stats-display" class="mt-4 text-lg">
正确题目数量: <span id="correct-count">0</span>
错误题目数量: <span id="wrong-count">0</span>
</div>
<div id="wrong-questions" class="mt-4 hidden">
<h2 class="text-xl font-bold mb-2">错误题目列表</h2>
<ul id="wrong-questions-list"></ul>
</div>
<div id="edit-container" class="bg-white p-8 rounded-lg shadow-md mt-8 hidden">
<h2 class="text-xl font-bold mb-4">编辑题目</h2>
<input type="text" id="edit-question" placeholder="题目内容" class="w-full p-2 border border-gray-300 rounded-md mb-2">
<input type="text" id="edit-image" placeholder="图片路径" class="w-full p-2 border border-gray-300 rounded-md mb-2">
<div id="edit-options" class="space-y-2"></div>
<button class="bg-green-500 text-white py-2 px-4 rounded-md">添加选项</button>
<div id="correct-answer-select" class="mt-4 hidden">
<label for="correct-answer" class="block text-sm font-medium text-gray-700">选择正确答案</label>
<select id="correct-answer" multiple class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"></select>
</div>
<div class="mt-4">
<label for="question-type" class="block text-sm font-medium text-gray-700">题目类型</label>
<select id="question-type" class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="single">单选题</option>
<option value="multiple">多选题</option>
<option value="fill">填空题</option>
<option value="judge">判断题</option>
</select>
</div>
<div class="mt-4">
<label for="question-category" class="block text-sm font-medium text-gray-700">题目类目</label>
<input type="text" id="question-category" placeholder="请输入类目" class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded-md ml-2">保存题目</button>
<button class="bg-red-500 text-white py-2 px-4 rounded-md ml-2">取消编辑</button>
</div>
<!-- 说明模态框 -->
<div id="instructions-modal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>功能说明</h2>
<p>1. 对于单选题和多选题,可以使用数字键盘(主键盘区和小键盘区)的 1 - 8 键选择答案。对于多选题,可连续按数字键选择多个选项。</p>
<p>2. 按下数字回车键能实现“提交答案”的功能。</p>
<p>3. 对于填空题,直接在输入框中输入答案后按提交。</p>
<p>4. 对于判断题,使用数字 1 代表正确,数字 2 代表错误。</p>
<p>5. 如果选错答案,正确答案会被框选并闪烁 3 秒后跳转到下一题。</p>
<p>6. 可以使用键盘上的上箭头键切换到上一题,下箭头键切换到下一题。</p>
<p>7. 按下数字 9 可实现随机一题功能。</p>
<p>8. 可以通过类目筛选功能,选择特定类目的题目进行刷题。</p>
<p>9. 题目配图固定尺寸显示,点击图片可放大查看。</p>
</div>
</div>
<!-- 放大图片模态框 -->
<div id="image-modal" class="modal">
<span id="close-modal">×</span>
<img id="modal-image" class="modal-content" src="">
</div>
</div>
<script>
// 初始化题目数据
let questions = [
{
question: "以下哪种是编程语言?",
options: ["HTML", "CSS", "JavaScript", "Photoshop"],
answer: ["JavaScript"],
type: "single",
category: "单选题",
image: ""
},
{
question: "以下哪些是数据库管理系统?",
options: ["Word", "Excel", "MySQL", "PowerPoint", "Oracle"],
answer: ["MySQL", "Oracle"],
type: "multiple",
category: "多选题",
image: ""
},
{
question: "名侦探柯南剧场版中柯南穿的鞋子是什么颜色的______。",
answer: ["红白"],
type: "fill",
category: "填空题",
image: ""
},
{
question: "‌奥特曼的星球叫做光之国‌,它位于M789星云(对/错)",
answer: ["错"],
type: "judge",
category: "判断题",
image: ""
}
];
let currentQuestion = 0;
let isEditing = false;
let editingIndex = null;
let correctCount = 0;
let wrongCount = 0;
let wrongQuestionList = [];
let filteredQuestions = questions;
// 加载当前题目
function loadQuestion() {
const questionNumberElement = document.getElementById('question-number');
const questionElement = document.getElementById('question');
const optionsElement = document.getElementById('options');
const imageElement = document.getElementById('question-image');
const current = filteredQuestions[currentQuestion];
questionNumberElement.textContent = `第 ${currentQuestion + 1} 题`;
questionElement.textContent = current.question;
optionsElement.innerHTML = '';
if (current.image) {
imageElement.src = current.image;
imageElement.style.display = 'block';
} else {
imageElement.style.display = 'none';
}
switch (current.type) {
case 'single':
current.options.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.innerHTML = `
<input type="radio" id="option-${index}" name="answer" value="${option}">
<label for="option-${index}">${index + 1}. ${option}</label>
`;
optionsElement.appendChild(optionElement);
});
break;
case 'multiple':
current.options.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.innerHTML = `
<input type="checkbox" id="option-${index}" name="answer" value="${option}">
<label for="option-${index}">${index + 1}. ${option}</label>
`;
optionsElement.appendChild(optionElement);
});
break;
case 'fill':
const inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.id = 'fill-answer';
inputElement.placeholder = '请输入答案';
inputElement.classList.add('w-full', 'p-2', 'border', 'border-gray-300', 'rounded-md', 'mb-2');
optionsElement.appendChild(inputElement);
break;
case 'judge':
const trueOption = document.createElement('div');
trueOption.innerHTML = `
<input type="radio" id="option-0" name="answer" value="对">
<label for="option-0">1. 对</label>
`;
const falseOption = document.createElement('div');
falseOption.innerHTML = `
<input type="radio" id="option-1" name="answer" value="错">
<label for="option-1">2. 错</label>
`;
optionsElement.appendChild(trueOption);
optionsElement.appendChild(falseOption);
break;
}
updateQuestionNav();
}
// 检查答案
function checkAnswer() {
const current = filteredQuestions[currentQuestion];
let selectedOptions;
switch (current.type) {
case 'single':
selectedOptions = [document.querySelector('input[name="answer"]:checked')?.value];
break;
case 'multiple':
selectedOptions = Array.from(document.querySelectorAll('input[name="answer"]:checked')).map(input => input.value);
break;
case 'fill':
selectedOptions = [document.getElementById('fill-answer').value];
break;
case 'judge':
selectedOptions = [document.querySelector('input[name="answer"]:checked')?.value];
break;
}
const resultElement = document.getElementById('result');
if ((current.type!== 'fill' && selectedOptions.length === 0) || (current.type === 'fill' && selectedOptions[0] === '')) {
resultElement.textContent = "请选择一个答案!";
resultElement.classList.add('text-red-500');
return;
}
const isCorrect = selectedOptions.sort().toString() === current.answer.sort().toString();
if (isCorrect) {
resultElement.textContent = "回答正确!";
resultElement.classList.add('text-green-500');
resultElement.classList.remove('text-red-500');
correctCount++;
document.getElementById('correct-count').textContent = correctCount;
goToNextQuestion();
} else {
resultElement.textContent = `回答错误,正确答案是:${current.answer.join(', ')}`;
resultElement.classList.add('text-red-500');
resultElement.classList.remove('text-green-500');
wrongCount++;
document.getElementById('wrong-count').textContent = wrongCount;
wrongQuestionList.push({
question: current.question,
userAnswer: selectedOptions.join(', '),
correctAnswer: current.answer.join(', ')
});
if (current.type!== 'fill') {
current.answer.forEach(answer => {
const answerIndex = current.type === 'judge'? (answer === '对'? 0 : 1) : current.options.indexOf(answer);
const answerLabel = document.querySelector(`label[for="option-${answerIndex}"]`);
answerLabel.classList.add('selected-correct', 'blink-border');
setTimeout(() => {
answerLabel.classList.remove('selected-correct', 'blink-border');
}, 3000);
});
}
setTimeout(() => {
goToNextQuestion();
}, 3000);
}
}
// 导入题目
function importQuestions() {
const fileInput = document.getElementById('import-file');
const file = fileInput.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
try {
questions = jsonData.map(item => {
const options = [];
let i = 1;
while (item[`option${i}`]) {
options.push(item[`option${i}`]);
i++;
}
const answer = item.answer.split(',');
return {
question: item.question,
options: options,
answer: answer,
type: item.type,
category: item.category,
image: item.image || ""
};
});
filteredQuestions = questions;
currentQuestion = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestionList = [];
document.getElementById('correct-count').textContent = correctCount;
document.getElementById('wrong-count').textContent = wrongCount;
document.getElementById('wrong-questions').classList.add('hidden');
document.getElementById('wrong-questions-list').innerHTML = '';
loadQuestion();
document.getElementById('question-container').style.display = 'block';
document.getElementById('result').textContent = '';
populateCategoryFilter();
} catch (error) {
alert('导入文件格式错误,请确保文件结构正确。');
}
};
reader.readAsArrayBuffer(file);
}
}
// 导出题目
function exportQuestions() {
try {
const exportData = [];
questions.forEach(question => {
const row = {
question: question.question,
answer: question.answer.join(','),
type: question.type,
category: question.category,
image: question.image
};
if (question.options) {
question.options.forEach((option, index) => {
row[`option${index + 1}`] = option;
});
}
exportData.push(row);
});
console.log('Export data:', exportData);
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Questions');
XLSX.writeFile(wb, 'questions.xlsx');
} catch (error) {
console.error('Export error:', error);
alert('导出题目时出现错误,请查看控制台信息。');
}
}
// 添加题目
function addQuestion() {
isEditing = false;
editingIndex = null;
document.getElementById('edit-question').value = '';
document.getElementById('edit-image').value = '';
document.getElementById('edit-options').innerHTML = '';
document.getElementById('correct-answer-select').classList.add('hidden');
document.getElementById('edit-container').classList.remove('hidden');
}
// 添加选项
function addOption() {
const optionsElement = document.getElementById('edit-options');
const optionInput = document.createElement('input');
optionInput.type = 'text';
optionInput.placeholder = '选项内容';
optionInput.classList.add('w-full', 'p-2', 'border', 'border-gray-300', 'rounded-md', 'mb-2');
optionsElement.appendChild(optionInput);
const optionCount = optionsElement.children.length;
if (optionCount > 0) {
document.getElementById('correct-answer-select').classList.remove('hidden');
}
const select = document.getElementById('correct-answer');
const option = document.createElement('option');
option.value = optionCount - 1;
option.textContent = `选项 ${optionCount}`;
select.appendChild(option);
}
// 保存题目
function saveQuestion() {
const questionInput = document.getElementById('edit-question');
const imageInput = document.getElementById('edit-image');
const optionsInputs = document.querySelectorAll('#edit-options input');
const question = questionInput.value;
const image = imageInput.value;
const options = [];
optionsInputs.forEach(input => {
if (input.value) {
options.push(input.value);
}
});
const correctAnswerIndices = Array.from(document.getElementById('correct-answer').selectedOptions).map(option => option.value);
const answer = correctAnswerIndices.map(index => options[index]);
const questionType = document.getElementById('question-type').value;
const questionCategory = document.getElementById('question-category').value;
if (question && ((questionType === 'fill' || questionType === 'judge') || options.length > 0) && answer.length > 0 && questionCategory) {
const newQuestion = {
question: question,
options: options,
answer: answer,
type: questionType,
category: questionCategory,
image: image
};
if (isEditing) {
questions[editingIndex] = newQuestion;
} else {
questions.push(newQuestion);
}
filteredQuestions = questions;
currentQuestion = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestionList = [];
document.getElementById('correct-count').textContent = correctCount;
document.getElementById('wrong-count').textContent = wrongCount;
document.getElementById('wrong-questions').classList.add('hidden');
document.getElementById('wrong-questions-list').innerHTML = '';
loadQuestion();
document.getElementById('edit-container').classList.add('hidden');
populateCategoryFilter();
} else {
alert('请输入题目、至少一个选项(填空题和判断题除外)、选择正确答案并填写题目类目。');
}
}
// 取消编辑
function cancelEdit() {
document.getElementById('edit-container').classList.add('hidden');
}
// 更新题目导航按钮
function updateQuestionNav() {
const nav = document.getElementById('question-nav');
nav.innerHTML = '';
filteredQuestions.forEach((_, index) => {
const button = document.createElement('button');
button.textContent = index + 1;
button.classList.add('bg-gray-300', 'text-black', 'py-2', 'px-4', 'rounded-md');
if (index === currentQuestion) {
button.classList.add('bg-blue-500', 'text-white');
}
button.addEventListener('click', () => {
currentQuestion = index;
loadQuestion();
document.getElementById('result').textContent = '';
});
nav.appendChild(button);
});
}
// 上一题
function goToPrevQuestion() {
if (currentQuestion > 0) {
currentQuestion--;
loadQuestion();
document.getElementById('result').textContent = '';
}
}
// 下一题
function goToNextQuestion() {
if (currentQuestion < filteredQuestions.length - 1) {
currentQuestion++;
loadQuestion();
document.getElementById('result').textContent = '';
} else {
document.getElementById('result').textContent = `所有题目已完成!正确题目数量: ${correctCount},错误题目数量: ${wrongCount}`;
document.getElementById('question-container').style.display = 'none';
if (wrongCount > 0) {
const wrongQuestionsList = document.getElementById('wrong-questions-list');
wrongQuestionsList.innerHTML = '';
wrongQuestionList.forEach(item => {
const listItem = document.createElement('li');
listItem.innerHTML = `
<p><strong>题目:</strong> ${item.question}</p>
<p><strong>你的答案:</strong> ${item.userAnswer}</p>
<p><strong>正确答案:</strong> <span class="text-red-500">${item.correctAnswer}</span></p>
`;
wrongQuestionsList.appendChild(listItem);
});
document.getElementById('wrong-questions').classList.remove('hidden');
}
}
}
// 随机一题
function goToRandomQuestion() {
currentQuestion = Math.floor(Math.random() * filteredQuestions.length);
loadQuestion();
document.getElementById('result').textContent = '';
}
// 结束答题
function endExam() {
document.getElementById('result').textContent = `所有题目已完成!正确题目数量: ${correctCount},错误题目数量: ${wrongCount}`;
document.getElementById('question-container').style.display = 'none';
if (wrongCount > 0) {
const wrongQuestionsList = document.getElementById('wrong-questions-list');
wrongQuestionsList.innerHTML = '';
wrongQuestionList.forEach(item => {
const listItem = document.createElement('li');
listItem.innerHTML = `
<p><strong>题目:</strong> ${item.question}</p>
<p><strong>你的答案:</strong> ${item.userAnswer}</p>
<p><strong>正确答案:</strong> <span class="text-red-500">${item.correctAnswer}</span></p>
`;
wrongQuestionsList.appendChild(listItem);
});
document.getElementById('wrong-questions').classList.remove('hidden');
}
}
// 重头答题
function restartExam() {
currentQuestion = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestionList = [];
document.getElementById('correct-count').textContent = correctCount;
document.getElementById('wrong-count').textContent = wrongCount;
document.getElementById('wrong-questions').classList.add('hidden');
document.getElementById('wrong-questions-list').innerHTML = '';
document.getElementById('question-container').style.display = 'block';
document.getElementById('result').textContent = '';
loadQuestion();
}
// 处理拖放事件
const dropArea = document.getElementById('drop-area');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropArea.style.borderColor = 'blue';
}
function unhighlight() {
dropArea.style.borderColor = '#ccc';
}
dropArea.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
const fileInput = document.getElementById('import-file');
fileInput.files = files;
importQuestions();
}
}
// 监听键盘事件【这个位置开始改快捷键按钮】
window.addEventListener('keydown', function (event) {
if (event.code === 'Enter' || event.code === 'NumpadEnter' || event.code === 'Return') {
checkAnswer();
} else if (/^(Digit|Numpad)[1-9]$/.test(event.code)) {
const optionIndex = parseInt(event.code.slice(-1)) - 1;
const current = filteredQuestions[currentQuestion];
if (current.type === 'single' || current.type === 'multiple') {
const option = document.getElementById(`option-${optionIndex}`);
if (option) {
if (current.type === 'single') {
option.checked = true;
checkAnswer();
} else {
option.checked = !option.checked;
}
}
} else if (current.type === 'judge') {
if (optionIndex === 0) {
document.getElementById('option-0').checked = true;
} else if (optionIndex === 1) {
document.getElementById('option-1').checked = true;
}
checkAnswer();
} else if (event.code === 'Digit9' || event.code === 'Numpad9') {
goToRandomQuestion();
}
} else if (event.code === 'ArrowUp') {
goToPrevQuestion();
} else if (event.code === 'ArrowDown') {
goToNextQuestion();
}
});
// 显示说明模态框
function showInstructions() {
const modal = document.getElementById('instructions-modal');
modal.style.display = "block";
}
// 关闭说明模态框
const instructionsCloseBtn = document.querySelector('#instructions-modal .close');
instructionsCloseBtn.addEventListener('click', function () {
const modal = document.getElementById('instructions-modal');
modal.style.display = "none";
});
// 点击说明模态框外部关闭
window.addEventListener('click', function (event) {
const modal = document.getElementById('instructions-modal');
if (event.target == modal) {
modal.style.display = "none";
}
});
// 点击图片放大
const questionImage = document.getElementById('question-image');
const imageModal = document.getElementById('image-modal');
const modalImage = document.getElementById('modal-image');
const closeModal = document.getElementById('close-modal');
questionImage.addEventListener('click', function () {
if (questionImage.style.display!== 'none') {
imageModal.style.display = "block";
modalImage.src = questionImage.src;
}
});
closeModal.addEventListener('click', function () {
imageModal.style.display = "none";
});
// 点击放大图片模态框外部关闭
window.addEventListener('click', function (event) {
if (event.target == imageModal) {
imageModal.style.display = "none";
}
});
// 填充类目筛选器
function populateCategoryFilter() {
const categoryFilter = document.getElementById('category-filter');
categoryFilter.innerHTML = '<option value="">所有类目</option>';
const categories = [...new Set(questions.map(question => question.category))];
categories.forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categoryFilter.appendChild(option);
});
}
// 根据类目筛选题目
function filterQuestionsByCategory() {
const selectedCategory = document.getElementById('category-filter').value;
if (selectedCategory === '') {
filteredQuestions = questions;
} else {
filteredQuestions = questions.filter(question => question.category === selectedCategory);
}
currentQuestion = 0;
loadQuestion();
}
// 初始化类目筛选器
populateCategoryFilter();
// 加载第一题
loadQuestion();
</script>
</body>
</html>
=======================================
2025年4月5日 22:21:13
更新到了V3版本新增填空题、判断题。
有其他需求可以根据上面代码改
单选题是圆形框框,多选框是正方形的勾选框。
功能改动如下1. 新增填空题、判断题2. 按下回车键能实现“提交答案”的功能。按键的热键可在代码中的705行代码下修改,我特地标注了注释...
=======================
2025年4月6日 00:51:40
.又双叒叕更新到了V4版本
导入规则的数组能够直接以 1,2,3,4 等形式引用选项答案
大部分数组改中文,会比较直观...
应该是最后亿次更新了吧......
============================
2025年5月5日 00:30:46
新增功能:
1.题目序号放右侧,可折叠
2.错题回溯练习
3.答案闪烁提示可开关,
4.题目结构加个题目解析
目前感觉这样子也够用了大小不到30kb
本地下载:https://zhenghui.lanzouu.com/ifFOf2vdsu4j
题库代码生成器:https://zhenghui.lanzouu.com/iiaeV2venfwd【用于代码中添加预设题库】
题库代码生成器网页版:https://share.htmlput.com/p/7fw6id77sa?lang=zh
题库网页版:https://share.htmlput.com/p/6h6wainxi3?lang=zh访问网页可在手机浏览器上导入题库答题
|
免费评分
-
查看全部评分
|