|
|
|
@ -0,0 +1,365 @@
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>课堂随机点名系统</title>
|
|
|
|
|
<link rel="icon" href="static/logo.ico" type="image/x-icon">
|
|
|
|
|
<style>
|
|
|
|
|
html,
|
|
|
|
|
body {
|
|
|
|
|
height: 100%;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
body {
|
|
|
|
|
font-family: Arial, sans-serif;
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-top: 50px;
|
|
|
|
|
background-image: url('/static/background.png'); /* 背景图片路径 */
|
|
|
|
|
background-size: cover;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-position: center;
|
|
|
|
|
}
|
|
|
|
|
.card {
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.8); /* 卡片背景色 */
|
|
|
|
|
border-radius: 10px; /* 圆角 */
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* 阴影 */
|
|
|
|
|
margin: 20px auto; /* 上下间距和自动水平居中 */
|
|
|
|
|
padding: 20px; /* 内边距 */
|
|
|
|
|
width: 300px; /* 卡片宽度 */
|
|
|
|
|
}
|
|
|
|
|
h1 {
|
|
|
|
|
color: whitesmoke;
|
|
|
|
|
font-size: 36px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
/* 按钮样式 */
|
|
|
|
|
.button {
|
|
|
|
|
display: block; /* 改为块级元素以实现纵向排列 */
|
|
|
|
|
width: 100%; /* 设置按钮宽度为 100% */
|
|
|
|
|
margin: 15px 0; /* 上下间距 */
|
|
|
|
|
padding: 15px; /* 增加上边距和下边距 */
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
background-color: #4CAF50;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background-color 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
.button:hover {
|
|
|
|
|
background-color: #45a049;
|
|
|
|
|
}
|
|
|
|
|
.button:active {
|
|
|
|
|
transform: scale(0.95);
|
|
|
|
|
}
|
|
|
|
|
.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.8);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transition: opacity 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
.modal.show {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
.modal-content {
|
|
|
|
|
background-color: #fefefe;
|
|
|
|
|
margin: 15% auto;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border: 1px solid #888;
|
|
|
|
|
width: 80%;
|
|
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
|
|
|
|
transition: transform 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
.modal-content.show {
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
}
|
|
|
|
|
.close {
|
|
|
|
|
color: #aaa;
|
|
|
|
|
float: right;
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.close:hover,
|
|
|
|
|
.close:focus {
|
|
|
|
|
color: black;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
.disabled {
|
|
|
|
|
opacity: 0.5; /* 设置透明度为 0.5 以表示禁用状态 */
|
|
|
|
|
pointer-events: none; /* 禁用事件 */
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>课堂随机点名系统</h1>
|
|
|
|
|
<img src="/static/kfc_cat.jpg" alt="Logo" style="width: 88px; height: auto;">
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<button class="button" data-action="upload">上传学生名单</button>
|
|
|
|
|
<button class="button" data-action="roll-call">随机点名</button>
|
|
|
|
|
<button class="button" data-action="view-rank">查看积分排行</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="myModal" class="modal">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<span id="closeModal" class="close">×</span>
|
|
|
|
|
<p id="rollResult"></p>
|
|
|
|
|
<form id="rollCallForm">
|
|
|
|
|
<p>是否来上课了?
|
|
|
|
|
<label><input type="radio" name="isCome" value="yes" onclick="toggleQuestionOptions()"> 来了</label>
|
|
|
|
|
<label><input type="radio" name="isCome" value="no" onclick="toggleQuestionOptions()"> 没来</label>
|
|
|
|
|
</p>
|
|
|
|
|
<div id="questionOptions">
|
|
|
|
|
<p>是否能够重复问题?
|
|
|
|
|
<label><input type="radio" name="canRepeat" value="yes"> 能</label>
|
|
|
|
|
<label><input type="radio" name="canRepeat" value="no"> 不能</label>
|
|
|
|
|
</p>
|
|
|
|
|
<p>是否能够正确回答问题?
|
|
|
|
|
<label><input type="radio" name="answerCorrect" value="correct"> 完全正确</label>
|
|
|
|
|
<label><input type="radio" name="answerCorrect" value="partial"> 部分正确</label>
|
|
|
|
|
<label><input type="radio" name="answerCorrect" value="incorrect"> 错误</label>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="button" id="submitRollCall">提交</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
const apiBaseUrl = "http://127.0.0.1:8000"; /* 部署到服务器需要修改为http://8.130.115.98:8080 */
|
|
|
|
|
var modal = document.getElementById("myModal");
|
|
|
|
|
var rollResultElement = document.getElementById("rollResult");
|
|
|
|
|
var span = document.getElementById("closeModal");
|
|
|
|
|
|
|
|
|
|
// 关闭模态框的函数
|
|
|
|
|
function closeModal() {
|
|
|
|
|
modal.classList.remove('show');
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
modal.style.display = "none";
|
|
|
|
|
}, 300);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
span.onclick = closeModal;
|
|
|
|
|
window.onclick = function (event) {
|
|
|
|
|
if (event.target == modal) {
|
|
|
|
|
closeModal();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let studentData = [];
|
|
|
|
|
|
|
|
|
|
// 处理上传学生名单的函数
|
|
|
|
|
function uploadList() {
|
|
|
|
|
let fileInput = document.createElement('input');
|
|
|
|
|
fileInput.type = 'file';
|
|
|
|
|
fileInput.accept = '.xlsx';
|
|
|
|
|
fileInput.onchange = function () {
|
|
|
|
|
let formData = new FormData();
|
|
|
|
|
formData.append('file', fileInput.files[0]);
|
|
|
|
|
fetch(apiBaseUrl + '/upload', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: formData
|
|
|
|
|
})
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('Network response was not ok');
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(data => {
|
|
|
|
|
alert(data.message);
|
|
|
|
|
if (data.message === "名单上传成功!") {
|
|
|
|
|
studentData = data.students;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('上传失败,请检查网络连接或文件格式。');
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
fileInput.click();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理随机点名的函数
|
|
|
|
|
function rollCall() {
|
|
|
|
|
fetch(apiBaseUrl + '/roll?enable_random_events=true')
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('Network response was not ok');
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(responseData => {
|
|
|
|
|
if (responseData.message) {
|
|
|
|
|
alert(responseData.message);
|
|
|
|
|
} else {
|
|
|
|
|
sessionStorage.setItem('lastCalledStudentId', responseData.学号);
|
|
|
|
|
rollResultElement.innerText = `被点名的学生是:${responseData.学号}--${responseData.姓名}`;
|
|
|
|
|
modal.classList.add('show');
|
|
|
|
|
modal.style.display = "block";
|
|
|
|
|
if (responseData.转移权) {
|
|
|
|
|
showTransferOptions(responseData.学号);
|
|
|
|
|
}
|
|
|
|
|
if (responseData.随机事件) {
|
|
|
|
|
alert(`随机事件触发:${responseData.随机事件}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert(`无法完成点名,请检查网络连接或服务器状态。详细信息:${error.message}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 切换问题选项的可用性
|
|
|
|
|
function toggleQuestionOptions() {
|
|
|
|
|
const form = document.getElementById("rollCallForm");
|
|
|
|
|
const isCome = form.elements["isCome"].value;
|
|
|
|
|
const questionOptions = document.getElementById("questionOptions");
|
|
|
|
|
if (isCome === "no") {
|
|
|
|
|
questionOptions.classList.add("disabled");
|
|
|
|
|
Array.from(questionOptions.querySelectorAll('input[type="radio"]')).forEach(input => {
|
|
|
|
|
input.checked = false;
|
|
|
|
|
input.disabled = true;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
questionOptions.classList.remove("disabled");
|
|
|
|
|
Array.from(questionOptions.querySelectorAll('input[type="radio"]')).forEach(input => {
|
|
|
|
|
input.disabled = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示转移选项的函数
|
|
|
|
|
function showTransferOptions(currentStudentId) {
|
|
|
|
|
const transferModal = document.createElement('div');
|
|
|
|
|
transferModal.className = 'modal transfer-modal';
|
|
|
|
|
const transferContent = document.createElement('div');
|
|
|
|
|
transferContent.className = 'modal-content';
|
|
|
|
|
transferContent.innerHTML = `<p>请选择转移的学生:</p>`;
|
|
|
|
|
studentData.forEach(student => {
|
|
|
|
|
if (student.学号 !== currentStudentId) {
|
|
|
|
|
const button = document.createElement('button');
|
|
|
|
|
button.innerText = `${student.姓名} (${student.学号})`;
|
|
|
|
|
button.onclick = function () {
|
|
|
|
|
alert(`转移到学生:${student.姓名} (${student.学号})`);
|
|
|
|
|
transferModal.style.display = "none";
|
|
|
|
|
modal.style.display = "none";
|
|
|
|
|
sessionStorage.setItem('lastCalledStudentId', student.学号);
|
|
|
|
|
rollCall();
|
|
|
|
|
};
|
|
|
|
|
transferContent.appendChild(button);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
transferModal.appendChild(transferContent);
|
|
|
|
|
document.body.appendChild(transferModal);
|
|
|
|
|
transferModal.classList.add('show');
|
|
|
|
|
transferModal.style.display = "block";
|
|
|
|
|
const closeButton = document.createElement('span');
|
|
|
|
|
closeButton.className = 'close';
|
|
|
|
|
closeButton.innerHTML = '×';
|
|
|
|
|
closeButton.onclick = function () {
|
|
|
|
|
transferModal.style.display = "none";
|
|
|
|
|
};
|
|
|
|
|
transferContent.appendChild(closeButton);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提交点名评估的函数
|
|
|
|
|
document.getElementById("submitRollCall").onclick = function () {
|
|
|
|
|
const form = document.getElementById("rollCallForm");
|
|
|
|
|
const isCome = form.elements["isCome"].value === "yes";
|
|
|
|
|
const canRepeat = form.elements["canRepeat"].value === "yes";
|
|
|
|
|
const answerCorrect = form.elements["answerCorrect"].value === "correct" ? "完全正确" :
|
|
|
|
|
(form.elements["answerCorrect"].value === "partial" ? "部分正确" : "错误");
|
|
|
|
|
const studentId = sessionStorage.getItem('lastCalledStudentId');
|
|
|
|
|
if (!studentId) {
|
|
|
|
|
alert('没有找到被点名的学生 ID,请重新点名。');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const dataToSend = {
|
|
|
|
|
student_id: studentId,
|
|
|
|
|
is_come: isCome,
|
|
|
|
|
can_repeat: canRepeat,
|
|
|
|
|
answer_correct: answerCorrect
|
|
|
|
|
};
|
|
|
|
|
fetch(apiBaseUrl + '/evaluate-answer', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(dataToSend),
|
|
|
|
|
})
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('Network response was not ok');
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(updatedData => {
|
|
|
|
|
if (updatedData.success) {
|
|
|
|
|
alert('回答已评估!');
|
|
|
|
|
} else {
|
|
|
|
|
alert('回答评估失败!');
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('无法完成评估,请检查网络连接或服务器状态。');
|
|
|
|
|
});
|
|
|
|
|
closeModal();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 处理查看积分排行的函数
|
|
|
|
|
function viewRank() {
|
|
|
|
|
fetch(apiBaseUrl + '/rank')
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('Network response was not ok');
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (data.message) {
|
|
|
|
|
alert(data.message);
|
|
|
|
|
} else {
|
|
|
|
|
let rankList = "积分排行:\n";
|
|
|
|
|
data.forEach((student, index) => {
|
|
|
|
|
rankList += `${index + 1}. 学号:${student.学号} 姓名:${student.姓名} 积分:${student.积分}\n`;
|
|
|
|
|
});
|
|
|
|
|
alert(rankList);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('无法完成操作,请检查网络连接或服务器状态。');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 为按钮添加点击事件处理
|
|
|
|
|
document.querySelectorAll('.button').forEach(button => {
|
|
|
|
|
button.addEventListener('click', function () {
|
|
|
|
|
const action = this.dataset.action;
|
|
|
|
|
if (action === 'upload') {
|
|
|
|
|
uploadList();
|
|
|
|
|
} else if (action === 'roll-call') {
|
|
|
|
|
rollCall();
|
|
|
|
|
} else if (action === 'view-rank') {
|
|
|
|
|
viewRank();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|