<!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" onchange="importQuestions()" 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" onclick="exportQuestions()">导出题目</button>
<button class="bg-green-500 text-white py-2 px-4 rounded-md" onclick="addQuestion()">添加题目</button>
<button class="bg-purple-500 text-white py-2 px-4 rounded-md" onclick="restartExam()">重头答题</button>
<button class="bg-yellow-500 text-white py-2 px-4 rounded-md" onclick="showInstructions()">说明</button>
<button class="bg-orange-500 text-white py-2 px-4 rounded-md" onclick="redoWrongQuestions()">重做错题</button>
</div>
<select id="category-filter" onchange="filterQuestionsByCategory()" 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="题目内容" class="text-lg mb-4"></p>
<img id="question-image" class="question-image" src="" alt="Question Image" style="display: none;">
<div id="选项" 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" onclick="goToPrevQuestion()">上一题</button>
<button id="submit" class="bg-blue-500 text-white py-2 px-4 rounded-md mt-4" onclick="checkAnswer()">提交答案</button>
<button id="next-question" class="bg-green-500 text-white py-2 px-4 rounded-md mt-4" onclick="goToNextQuestion()">下一题</button>
<button id="random-question" class="bg-pink-500 text-white py-2 px-4 rounded-md mt-4" onclick="goToRandomQuestion()">随机一题</button>
<button id="end-exam" class="bg-red-500 text-white py-2 px-4 rounded-md mt-4" onclick="endExam()">结束答题</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>
题目总数: <span id="total-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-题目内容" placeholder="题目内容" class="w-full p-2 border border-gray-300 rounded-md mb-2">
<input type="text" id="edit-图片路径" placeholder="图片路径" class="w-full p-2 border border-gray-300 rounded-md mb-2">
<div id="edit-选项" class="space-y-2"></div>
<button class="bg-green-500 text-white py-2 px-4 rounded-md" onclick="addOption()">添加选项</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="题目类型" class="block text-sm font-medium text-gray-700">题目类型</label>
<select id="题目类型" 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="题目类目" class="block text-sm font-medium text-gray-700">题目类目</label>
<input type="text" id="题目类目" 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" onclick="saveQuestion()">保存题目</button>
<button class="bg-red-500 text-white py-2 px-4 rounded-md ml-2" onclick="cancelEdit()">取消编辑</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 = [
{
题目内容: "以下哪种是编程语言?",
选项: {
1: "HTML",
2: "CSS",
3: "JavaScript",
4: "Photoshop"
},
答案: ["3"],
题目类型: "single",
题目类目: "单选题",
图片路径: ""
},
{
题目内容: "以下哪些是数据库管理系统?",
选项: {
1: "Word",
2: "Excel",
3: "MySQL",
4: "PowerPoint",
5: "Oracle"
},
答案: ["3", "5"],
题目类型: "multiple",
题目类目: "多选题",
图片路径: ""
},
{
题目内容: "名侦探柯南剧场版中柯南穿的鞋子是什么颜色的______。",
答案: ["红白"],
题目类型: "fill",
题目类目: "填空题",
图片路径: ""
},
{
题目内容: "奥特曼的星球叫做光之国,它位于M789星云(对/错)",
答案: ["错"],
题目类型: "judge",
题目类目: "判断题",
图片路径: ""
}
];
let currentQuestionIndex = 0;
let correctCount = 0;
let wrongCount = 0;
let wrongQuestions = [];
let userAnswers = [];
// 更新题目总数显示
function updateTotalCount() {
document.getElementById('total-count').textContent = questions.length;
}
// 导入题目
function importQuestions() {
const input = document.getElementById('import-file');
const file = input.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 newQuestions = XLSX.utils.sheet_to_json(worksheet);
questions = questions.concat(newQuestions);
updateTotalCount();
renderQuestions();
};
reader.readAsArrayBuffer(file);
}
}
// 导出题目
function exportQuestions() {
const ws = XLSX.utils.json_to_sheet(questions);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Questions');
XLSX.writeFile(wb, 'questions.xlsx');
}
// 添加题目
function addQuestion() {
document.getElementById('edit-container').style.display = 'block';
}
// 保存题目
function saveQuestion() {
const questionContent = document.getElementById('edit-题目内容').value;
const imagePath = document.getElementById('edit-图片路径').value;
const questionType = document.getElementById('题目类型').value;
const questionCategory = document.getElementById('题目类目').value;
const options = {};
const optionInputs = document.querySelectorAll('#edit-选项 input');
optionInputs.forEach((input, index) => {
options[index + 1] = input.value;
});
const correctAnswerSelect = document.getElementById('correct-answer');
const correctAnswers = Array.from(correctAnswerSelect.selectedOptions).map(option => option.value);
const newQuestion = {
题目内容: questionContent,
选项: options,
答案: correctAnswers,
题目类型: questionType,
题目类目: questionCategory,
图片路径: imagePath
};
questions.push(newQuestion);
updateTotalCount();
cancelEdit();
renderQuestions();
}
// 取消编辑
function cancelEdit() {
document.getElementById('edit-container').style.display = 'none';
document.getElementById('edit-题目内容').value = '';
document.getElementById('edit-图片路径').value = '';
document.getElementById('edit-选项').innerHTML = '';
document.getElementById('correct-answer').innerHTML = '';
}
// 添加选项
function addOption() {
const optionDiv = document.createElement('div');
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');
optionDiv.appendChild(optionInput);
document.getElementById('edit-选项').appendChild(optionDiv);
const correctAnswerSelect = document.getElementById('correct-answer');
const optionIndex = correctAnswerSelect.options.length + 1;
const option = document.createElement('option');
option.value = optionIndex;
option.textContent = `选项 ${optionIndex}`;
correctAnswerSelect.appendChild(option);
document.getElementById('correct-answer-select').style.display = 'block';
}
// 渲染题目导航
function renderQuestionNav() {
const questionNav = document.getElementById('question-nav');
questionNav.innerHTML = '';
questions.forEach((question, 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 (wrongQuestions.includes(index)) {
button.classList.add('bg-red-500', 'text-white');
}
if (index === currentQuestionIndex) {
button.classList.add('bg-blue-500', 'text-white');
}
button.addEventListener('click', () => {
currentQuestionIndex = index;
renderQuestion();
});
questionNav.appendChild(button);
});
}
// 渲染当前题目
function renderQuestion() {
const question = questions[currentQuestionIndex];
const questionNumberElement = document.getElementById('question-number');
const questionContentElement = document.getElementById('题目内容');
const optionsElement = document.getElementById('选项');
const imageElement = document.getElementById('question-image');
questionNumberElement.textContent = `第 ${currentQuestionIndex + 1} 题`;
questionContentElement.textContent = question.题目内容;
optionsElement.innerHTML = '';
if (question.图片路径) {
imageElement.src = question.图片路径;
imageElement.style.display = 'block';
} else {
imageElement.style.display = 'none';
}
switch (question.题目类型) {
case 'single':
for (let key in question.选项) {
const optionElement = document.createElement('div');
optionElement.innerHTML = `
<input type="radio" id="option-${key}" name="answer" value="${question.选项[key]}">
<label for="option-${key}">${key}. ${question.选项[key]}</label>
`;
optionsElement.appendChild(optionElement);
}
break;
case 'multiple':
for (let key in question.选项) {
const optionElement = document.createElement('div');
optionElement.innerHTML = `
<input type="checkbox" id="option-${key}" name="answer" value="${question.选项[key]}">
<label for="option-${key}">${key}. ${question.选项[key]}</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-对" name="answer" value="对">
<label for="option-对">1. 对</label>
`;
const falseOption = document.createElement('div');
falseOption.innerHTML = `
<input type="radio" id="option-错" name="answer" value="错">
<label for="option-错">2. 错</label>
`;
optionsElement.appendChild(trueOption);
optionsElement.appendChild(falseOption);
break;
}
renderQuestionNav();
}
// 检查答案
function checkAnswer() {
const question = questions[currentQuestionIndex];
let selectedOptions;
switch (question.题目类型) {
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 ((question.题目类型!== 'fill' && selectedOptions.length === 0) || (question.题目类型 === 'fill' && selectedOptions[0] === '')) {
resultElement.textContent = "请选择一个答案!";
resultElement.classList.add('text-red-500');
return;
}
let correctAnswerValues = [];
if (question.题目类型!== 'fill' && question.题目类型!== 'judge') {
correctAnswerValues = question.答案.map(key => question.选项[key]);
} else {
correctAnswerValues = question.答案;
}
const isCorrect = selectedOptions.sort().toString() === correctAnswerValues.sort().toString();
userAnswers[currentQuestionIndex] = selectedOptions;
if (isCorrect) {
resultElement.textContent = "回答正确!";
resultElement.classList.add('text-green-500');
resultElement.classList.remove('text-red-500');
correctCount++;
document.getElementById('correct-count').textContent = correctCount;
} else {
resultElement.textContent = `回答错误,正确答案是:${correctAnswerValues.join(', ')}`;
resultElement.classList.add('text-red-500');
resultElement.classList.remove('text-green-500');
wrongCount++;
document.getElementById('wrong-count').textContent = wrongCount;
if (!wrongQuestions.includes(currentQuestionIndex)) {
wrongQuestions.push(currentQuestionIndex);
}
if (question.题目类型!== 'fill') {
correctAnswerValues.forEach(answer => {
const optionKey = Object.keys(question.选项).find(key => question.选项[key] === answer);
const answerLabel = document.querySelector(`label[for="option-${optionKey}"]`);
answerLabel.classList.add('selected-correct', 'blink-border');
setTimeout(() => {
answerLabel.classList.remove('selected-correct', 'blink-border');
}, 3000);
});
}
setTimeout(() => {
goToNextQuestion();
}, 3000);
}
}
// 上一题
function goToPrevQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
renderQuestion();
document.getElementById('result').textContent = '';
}
}
// 下一题
function goToNextQuestion() {
if (currentQuestionIndex < questions.length - 1) {
currentQuestionIndex++;
renderQuestion();
document.getElementById('result').textContent = '';
} else {
document.getElementById('result').textContent = `所有题目已完成!正确题目数量: ${correctCount},错误题目数量: ${wrongCount}`;
document.getElementById('question-container').style.display = 'none';
showWrongQuestions();
}
}
// 随机一题
function goToRandomQuestion() {
currentQuestionIndex = Math.floor(Math.random() * questions.length);
renderQuestion();
document.getElementById('result').textContent = '';
}
// 结束答题
function endExam() {
document.getElementById('result').textContent = `所有题目已完成!正确题目数量: ${correctCount},错误题目数量: ${wrongCount}`;
document.getElementById('question-container').style.display = 'none';
showWrongQuestions();
}
// 重头答题
function restartExam() {
currentQuestionIndex = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestions = [];
userAnswers = [];
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 = '';
renderQuestion();
}
// 显示错误题目列表
function showWrongQuestions() {
const wrongQuestionsContainer = document.getElementById('wrong-questions');
wrongQuestionsContainer.classList.remove('hidden');
const wrongQuestionsList = document.getElementById('wrong-questions-list');
wrongQuestionsList.innerHTML = '';
wrongQuestions.forEach((index) => {
const question = questions[index];
const userAnswer = userAnswers[index];
let correctAnswerValues = [];
if (question.题目类型!== 'fill' && question.题目类型!== 'judge') {
correctAnswerValues = question.答案.map(key => question.选项[key]);
} else {
correctAnswerValues = question.答案;
}
const listItem = document.createElement('li');
listItem.textContent = `题目内容: ${question.题目内容},你的答案: ${userAnswer.join(', ')},正确答案: ${correctAnswerValues.join(', ')}`;
wrongQuestionsList.appendChild(listItem);
});
}
// 处理拖放事件
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));
const question = questions[currentQuestionIndex];
if (question.题目类型 === 'single' || question.题目类型 === 'multiple') {
const option = document.getElementById(`option-${optionIndex}`);
if (option) {
if (question.题目类型 === 'single') {
option.checked = true;
checkAnswer();
} else {
option.checked = !option.checked;
}
}
} else if (question.题目类型 === 'judge') {
if (optionIndex === 1) {
document.getElementById('option-对').checked = true;
} else if (optionIndex === 2) {
document.getElementById('option-错').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";
});
// 点击放大图片
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 () {
imageModal.style.display = "block";
modalImage.src = questionImage.src;
});
closeModal.addEventListener('click', function () {
imageModal.style.display = "none";
});
// 填充类目筛选选项
function populateCategoryFilter() {
const categoryFilter = document.getElementById('category-filter');
categoryFilter.innerHTML = '<option value="">所有类目</option>';
const categories = new Set();
questions.forEach(question => {
categories.add(question.题目类目);
});
categories.forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categoryFilter.appendChild(option);
});
}
// 根据类目筛选题目
function filterQuestionsByCategory() {
const categoryFilter = document.getElementById('category-filter').value;
if (categoryFilter === '') {
filteredQuestions = questions;
} else {
filteredQuestions = questions.filter(question => question.题目类目 === categoryFilter);
}
currentQuestionIndex = 0;
renderQuestions();
}
// 重做错题
function redoWrongQuestions() {
if (wrongQuestions.length === 0) {
alert('没有错题需要重做。');
return;
}
questions = wrongQuestions.map(index => questions[index]);
currentQuestionIndex = 0;
correctCount = 0;
wrongCount = 0;
wrongQuestions = [];
userAnswers = [];
document.getElementById('correct-count').textContent = correctCount;
document.getElementById('wrong-count').textContent = wrongCount;
document.getElementById('question-container').style.display = 'block';
document.getElementById('result').textContent = '';
renderQuestions();
}
// 初始化页面
updateTotalCount();
renderQuestion();
populateCategoryFilter();
</script>
</body>
</html>
格式化
支持Emmet,输入 p 后按 Tab键试试吧!
<head> <script src="https://cdn... </head>
<body>
</body>