<!
DOCTYPE
html>
<
html
lang
=
"en"
>
<
head
>
<
meta
charset
=
"UTF-8"
>
<
meta
name
=
"viewport"
content
=
"width=device-width, initial-scale=1.0"
>
<
script
src
=
"lib/tailwindcss3.4.16"
></
script
>
<
link
href
=
"lib/Font Awesome Free 6.7.2-all.min.css"
rel
=
"stylesheet"
>
<
title
>题库软件</
title
>
<
style
>
.question-label {
width: 2.5rem;
height: 2.5rem;
display: inline-flex;
align-items: center;
justify-content: center;
margin: 0.25rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: 1px solid #ccc;
border-radius: 0;
}
.question-label.current {
background-color: #3b82f6;
color: white;
border-radius: 1.5rem;
}
.question-label.correct {
background-color: #22c55e;
color: white;
}
.question-label.incorrect {
background-color: #ef4444;
color: white;
}
#question-content {
min-height: 200px;
max-height: calc(100vh - 120px);
overflow-y: auto;
}
input[type="text"],
textarea {
border: 1px solid #ccc;
padding: 0.5rem;
margin-bottom: 0.5rem;
height: 2.5rem;
}
textarea {
height: 6rem;
}
input[type="radio"],
input[type="checkbox"] {
margin-right: 1rem;
}
#question-content h2 {
margin-bottom: 0.5rem;
font-size: 1.25rem;
}
#question-content label {
font-size: 1.25rem;
}
#question-types > div {
margin-bottom: 1.5rem;
}
.file-selection-group {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.custom-file-input {
position: relative;
flex: 1;
min-width: 120px;
}
.custom-file-input input[type="file"] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.custom-file-input label {
display: block;
padding: 0.75rem 1rem;
background-color: #3b82f6;
color: white;
border-radius: 0.375rem;
text-align: center;
cursor: pointer;
transition: background-color 0.3s ease;
height: 100%;
box-sizing: border-box;
}
.custom-file-input label:hover {
background-color: #2563eb;
}
.custom-file-input label:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
}
.action-buttons {
display: flex;
gap: 0.5rem;
}
.action-buttons button {
padding: 0.75rem 1rem;
background-color: #4f46e5;
color: white;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.3s ease;
white-space: nowrap;
}
.action-buttons button:hover {
background-color: #4338ca;
}
.action-buttons button:disabled {
background-color: #a0aec0;
cursor: not-allowed;
}
#save-btn {
background-color: #22c55e;
}
#reset-btn {
background-color: #ef4444;
}
#selected-file-path {
padding: 0.75rem 0rem;
font-size: 1rem;
color: red;
}
#fixed-buttons {
display: flex;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
</
style
>
</
head
>
<
body
class
=
"bg-gray-100"
>
<
div
class
=
"flex h-screen"
>
<
div
class
=
"w-1/4 bg-white p-4 overflow-y-auto"
id
=
"question-types-container"
>
<
div
id
=
"question-types"
></
div
>
</
div
>
<
div
class
=
"w-3/4 bg-gray-200 p-4"
>
<
div
id
=
"fixed-buttons"
>
<
div
class
=
"file-selection-group"
>
<
div
class
=
"custom-file-input"
>
<
input
type
=
"file"
id
=
"question-file-input"
accept
=
".txt,.json"
>
<
label
for
=
"question-file-input"
>选择题目数据文件</
label
>
</
div
>
<
div
class
=
"action-buttons"
>
<
button
id
=
"save-btn"
>保存状态</
button
>
<
button
id
=
"reset-btn"
>全部重置</
button
>
</
div
>
<
div
id
=
"selected-file-path"
></
div
>
</
div
>
</
div
>
<
div
id
=
"question-content"
class
=
"bg-white p-4 rounded shadow mb-4 overflow-y-auto"
style
=
"white-space: pre-wrap;"
></
div
>
<
div
class
=
"flex justify-start space-x-2"
>
<
button
id
=
"confirm-btn"
class
=
"bg-blue-500 text-white px-4 py-2 rounded"
>确定</
button
>
<
button
id
=
"clear-btn"
class
=
"bg-yellow-500 text-white px-4 py-2 rounded"
>清空</
button
>
<
button
id
=
"prev-btn"
class
=
"bg-gray-500 text-white px-4 py-2 rounded"
>上一题</
button
>
<
button
id
=
"next-btn"
class
=
"bg-gray-500 text-white px-4 py-2 rounded"
>下一题</
button
>
</
div
>
</
div
>
</
div
>
<
script
>
let currentType;
let currentIndex = 0;
let answered = {};
let userAnswers = {};
const confirmBtn = document.getElementById('confirm-btn');
const clearBtn = document.getElementById('clear-btn');
const questionTypesContainer = document.getElementById('question-types-container');
const questionFileInput = document.getElementById('question-file-input');
const selectedFilePathDiv = document.getElementById('selected-file-path');
const saveBtn = document.getElementById('save-btn');
const resetBtn = document.getElementById('reset-btn');
let questions = {};
let selectedFilePath = '';
const SAVE_KEY = 'questionnaire_state';
const savedState = localStorage.getItem(SAVE_KEY);
if (savedState) {
const {
savedQuestions,
savedCurrentType,
savedCurrentIndex,
savedAnswered,
savedUserAnswers,
savedFilePath
} = JSON.parse(savedState);
questions = savedQuestions;
currentType = savedCurrentType;
currentIndex = savedCurrentIndex;
answered = savedAnswered;
userAnswers = savedUserAnswers;
selectedFilePath = savedFilePath;
if (selectedFilePath) {
selectedFilePathDiv.textContent = `已选择文件: ${selectedFilePath}`;
}
initQuestionTypes();
showQuestion();
}
questionFileInput.addEventListener('change', function (event) {
const file = event.target.files[0];
if (file) {
selectedFilePath = file.path || file.name;
selectedFilePathDiv.textContent = `已选择文件: ${selectedFilePath}`;
const reader = new FileReader();
reader.onload = function (e) {
try {
questions = JSON.parse(e.target.result);
currentType = Object.keys(questions)[0];
currentIndex = 0;
answered = {};
userAnswers = {};
initQuestionTypes();
showQuestion();
} catch (error) {
alert('解析文件内容时出错,请确保文件内容为有效的 JSON 格式。');
}
};
reader.readAsText(file);
} else {
if (selectedFilePath) {
selectedFilePathDiv.textContent = `已选择文件: ${selectedFilePath}`;
} else {
selectedFilePathDiv.textContent = '';
}
}
});
function initQuestionTypes() {
const questionTypesDiv = document.getElementById('question-types');
questionTypesDiv.innerHTML = '';
for (const type in questions) {
const typeDiv = document.createElement('div');
typeDiv.innerHTML = `<
h3
class
=
"text-lg font-bold mb-2"
>${type}</
h3
>`;
for (let i = 0; i <
questions
[type].length; i++) {
const
label
=
document
.createElement('span');
label.classList.add('question-label');
label.textContent
=
i
+ 1;
label.dataset.type
= type;
label.dataset.index
= i;
label.addEventListener('click', () => {
currentType = type;
currentIndex = i;
showQuestion();
});
typeDiv.appendChild(label);
}
questionTypesDiv.appendChild(typeDiv);
}
}
function showQuestion() {
const questionContentDiv = document.getElementById('question-content');
questionContentDiv.innerHTML = '';
if (!questions ||!questions[currentType]) {
return;
}
const question = questions[currentType][currentIndex];
const questionTitle = document.createElement('h2');
questionTitle.textContent = `${currentType}(第 ${currentIndex + 1} 题):${question.question}`;
questionContentDiv.appendChild(questionTitle);
if (currentType === '单选题' || currentType === '多选题') {
for (let i = 0; i <
question.options.length
; i++) {
const
input
=
document
.createElement('input');
input.type
=
currentType
=== '单选题'? 'radio' : 'checkbox';
input.name
=
'option'
;
input.value
=
String
.fromCharCode(65 + i);
const
label
=
document
.createElement('label');
label.textContent
=
question
.options[i];
label.addEventListener('click', () => {
input.click();
});
questionContentDiv.appendChild(input);
questionContentDiv.appendChild(label);
questionContentDiv.appendChild(document.createElement('br'));
if (userAnswers[`${currentType}-${currentIndex}`]) {
if (Array.isArray(userAnswers[`${currentType}-${currentIndex}`])) {
input.checked = userAnswers[`${currentType}-${currentIndex}`].includes(input.value);
} else {
input.checked = userAnswers[`${currentType}-${currentIndex}`] === input.value;
}
}
}
} else if (currentType === '填空题') {
const blanks = question.question.match(/______/g).length;
for (let i = 0; i <
blanks
; i++) {
const
input
=
document
.createElement('input');
input.type
=
'text'
;
input.style.width
=
'100%'
;
input.name = `blank-${i}`;
questionContentDiv.appendChild(input);
questionContentDiv.appendChild(document.createElement('br'));
if (userAnswers[`${currentType}-${currentIndex}`] && userAnswers[`${currentType}-${currentIndex}`][i]) {
input.value
=
userAnswers
[`${currentType}-${currentIndex}`][i];
}
}
const
inputs
=
document
.querySelectorAll('input[name^="blank-"]');
inputs[0].focus();
} else if (currentType === '判断题') {
const
trueInput
=
document
.createElement('input');
trueInput.type
=
'radio'
;
trueInput.name
=
'option'
;
trueInput.value
=
'正确'
;
const
trueLabel
=
document
.createElement('label');
trueLabel.textContent
=
'正确'
;
trueLabel.addEventListener('click', () => {
trueInput.click();
});
const falseInput = document.createElement('input');
falseInput.type = 'radio';
falseInput.name = 'option';
falseInput.value = '错误';
const falseLabel = document.createElement('label');
falseLabel.textContent = '错误';
falseLabel.addEventListener('click', () => {
falseInput.click();
});
questionContentDiv.appendChild(trueInput);
questionContentDiv.appendChild(trueLabel);
questionContentDiv.appendChild(document.createElement('br'));
questionContentDiv.appendChild(falseInput);
questionContentDiv.appendChild(falseLabel);
if (userAnswers[`${currentType}-${currentIndex}`]!== undefined) {
trueInput.checked = userAnswers[`${currentType}-${currentIndex}`] === '正确';
falseInput.checked = userAnswers[`${currentType}-${currentIndex}`] === '错误';
}
} else if (currentType === '简答题') {
const textarea = document.createElement('textarea');
textarea.style.width = '100%';
textarea.name = 'answer';
questionContentDiv.appendChild(textarea);
textarea.focus();
if (userAnswers[`${currentType}-${currentIndex}`]) {
textarea.value = userAnswers[`${currentType}-${currentIndex}`];
}
}
const labels = document.querySelectorAll('.question-label');
labels.forEach(label => {
label.classList.remove('current', 'correct', 'incorrect');
if (label.dataset.type === currentType && parseInt(label.dataset.index) === currentIndex) {
label.classList.add('current');
const rect = label.getBoundingClientRect();
const containerRect = questionTypesContainer.getBoundingClientRect();
if (rect.top <
containerRect.top
|| rect.bottom > containerRect.bottom) {
label.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
if (answered[`${label.dataset.type}-${label.dataset.index}`]!== undefined) {
if (answered[`${label.dataset.type}-${label.dataset.index}`]) {
label.classList.add('correct');
} else {
label.classList.add('incorrect');
}
}
});
if (answered[`${currentType}-${currentIndex}`]!== undefined) {
const result = answered[`${currentType}-${currentIndex}`];
const resultDiv = document.createElement('div');
if (result) {
resultDiv.textContent = '回答正确!';
resultDiv.classList.add('text-green-500');
} else {
resultDiv.textContent = `回答错误,正确答案是:${Array.isArray(question.answer)? question.answer.join(', ') : question.answer}`;
resultDiv.classList.add('text-red-500');
}
questionContentDiv.appendChild(resultDiv);
}
}
function confirmAnswer() {
const question = questions[currentType][currentIndex];
let userAnswer;
if (currentType === '单选题' || currentType === '判断题') {
const inputs = document.querySelectorAll('input[name="option"]:checked');
userAnswer = inputs.length > 0? inputs[0].value : null;
} else if (currentType === '多选题') {
const inputs = document.querySelectorAll('input[name="option"]:checked');
userAnswer = [];
inputs.forEach(input => userAnswer.push(input.value));
} else if (currentType === '填空题') {
const inputs = document.querySelectorAll('input[name^="blank-"]');
userAnswer = [];
inputs.forEach(input => userAnswer.push(input.value.trim())); // 添加trim()去除空格
} else if (currentType === '简答题') {
const textarea = document.querySelector('textarea[name="answer"]');
userAnswer = textarea.value;
}
userAnswers[`${currentType}-${currentIndex}`] = userAnswer;
let isCorrect;
if (Array.isArray(question.answer)) {
if (currentType === '填空题') {
isCorrect = true;
for (let i = 0; i <
question.answer.length
; i++) {
if (userAnswer[i] !== question.answer[i]) {
isCorrect
=
false
;
break;
}
}
} else {
isCorrect
=
JSON
.stringify(userAnswer.sort()) === JSON.stringify(question.answer.sort());
}
} else {
isCorrect
=
userAnswer
=== question.answer;
}
answered[`${currentType}-${currentIndex}`] = isCorrect;
const
label
=
document
.querySelector(`.question-label[
data-type
=
"${currentType}"
][
data-index
=
"${currentIndex}"
]`);
if (isCorrect) {
label.classList.add('correct');
label.classList.remove('incorrect');
} else {
label.classList.add('incorrect');
label.classList.remove('correct');
}
showQuestion();
// 不论正确与否,都自动切换到下一题
if (currentIndex < questions[currentType].length - 1) {
currentIndex++;
} else {
const
types
=
Object
.keys(questions);
const
currentTypeIndex
=
types
.indexOf(currentType);
if (currentTypeIndex < types.length - 1) {
currentType
=
types
[currentTypeIndex + 1];
currentIndex
=
0
;
}
}
showQuestion();
}
function clearAnswer() {
userAnswers[`${currentType}-${currentIndex}`] = null;
answered[`${currentType}-${currentIndex}`] = undefined;
const
label
=
document
.querySelector(`.question-label[
data-type
=
"${currentType}"
][
data-index
=
"${currentIndex}"
]`);
label.classList.remove('correct', 'incorrect');
showQuestion();
}
function prevQuestion() {
if (currentIndex > 0) {
currentIndex--;
} else {
const types = Object.keys(questions);
const currentTypeIndex = types.indexOf(currentType);
if (currentTypeIndex > 0) {
currentType = types[currentTypeIndex - 1];
currentIndex = questions[currentType].length - 1;
}
}
showQuestion();
}
function nextQuestion() {
if (currentIndex <
questions
[currentType].length - 1) {
currentIndex++;
} else {
const
types
=
Object
.keys(questions);
const
currentTypeIndex
=
types
.indexOf(currentType);
if (currentTypeIndex < types.length - 1) {
currentType
=
types
[currentTypeIndex + 1];
currentIndex
=
0
;
}
}
showQuestion();
}
function saveState() {
if (!questions || Object.keys(questions).length === 0) {
alert('请先选择题目数据文件');
return;
}
const state = {
savedQuestions: questions,
savedCurrentType: currentType,
savedCurrentIndex: currentIndex,
savedAnswered: answered,
savedUserAnswers: userAnswers,
savedFilePath: selectedFilePath
};
localStorage.setItem(SAVE_KEY, JSON.stringify(state));
alert('答题状态已保存!');
}
function resetAll() {
if (!questions || Object.keys(questions).length === 0) {
alert('请先选择题目数据文件');
return;
}
if (confirm('确定要清空所有答案,恢复初始状态吗?')) {
currentIndex
=
0
;
answered = {};
userAnswers = {};
const
labels
=
document
.querySelectorAll('.question-label');
labels.forEach(label => {
label.classList.remove('current', 'correct', 'incorrect');
});
localStorage.removeItem(SAVE_KEY);
currentType = Object.keys(questions)[0];
currentIndex = 0;
showQuestion();
}
}
document.addEventListener('keydown', function (event) {
if (Object.keys(questions).length > 0) {
if ((event.key === 'Enter' || event.code === 'Space') && (currentType === '判断题' || currentType === '单选题' || currentType === '多选题')) {
event.preventDefault(); // 阻止默认行为
confirmAnswer();
} else if ((event.key === 'Enter') && (currentType === '填空题' || currentType === '简答题')) {
event.preventDefault(); // 阻止默认行为
confirmAnswer();
} else if (event.key === 'Escape') {
event.preventDefault();
clearAnswer();
} else if (currentType === '判断题' && (event.key === '1' || event.key === '2')) {
event.preventDefault();
const option = event.key === '1'? '正确' : '错误';
const inputs = document.querySelectorAll('input[name="option"]');
inputs.forEach(input => {
if (input.value === option) {
input.checked = true;
}
});
} else if ((currentType === '单选题' || currentType === '多选题') && /^[1-9]$/.test(event.key)) {
event.preventDefault();
const index = parseInt(event.key) - 1;
const option = String.fromCharCode(65 + index);
const inputs = document.querySelectorAll('input[name="option"]');
inputs.forEach(input => {
if (input.value === option) {
if (currentType === '单选题') {
inputs.forEach(inp => inp.checked = false);
}
input.checked =!input.checked;
}
});
}
}
if (event.key === 'ArrowLeft') {
event.preventDefault();
prevQuestion();
} else if (event.key === 'ArrowRight') {
event.preventDefault();
nextQuestion();
}
});
confirmBtn.addEventListener('click', confirmAnswer);
clearBtn.addEventListener('click', clearAnswer);
const prevBtn = document.getElementById('prev-btn');
prevBtn.addEventListener('click', prevQuestion);
const nextBtn = document.getElementById('next-btn');
nextBtn.addEventListener('click', nextQuestion);
saveBtn.addEventListener('click', saveState);
resetBtn.addEventListener('click', resetAll);
</
script
>
</
body
>
</
html
>