You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

495 lines
20 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随机点名系统</title>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/ionicons/4.5.6/css/ionicons.min.css'>
<link rel="stylesheet" href="styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Long+Cang&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&display=swap" rel="stylesheet">
</head>
<body>
<h1>随机点名系统</h1>
<div id="message" style="display:none; position:fixed; top:20px; left:50%; transform:translateX(-50%); background-color:rgba(0, 0, 0, 0.5); color:white; padding:10px; border-radius:5px; white-space:nowrap;">
</div>
<div class="container">
<div class="file-upload">
<label class="file-label" for="fileInput" style="cursor: pointer;">
<img src="file-icon.svg" alt="选择文件" style="width: 40px; height: 40px;" />
<input type="file" id="fileInput" style="display: none;" onchange="updateFileName()" />
</label>
<span id="fileName">未选择文件</span>
<button id="uploadButton" onclick="uploadFile()"style="background: none; border: none; padding: 0; cursor: pointer;">
<img src="upload-icon.svg" alt="导入" style="width: 40px; height: 40px;" />
</button>
</div>
<div class="button-row">
<button id="drawButton" onclick="startDraw()" disabled>开始点名</button>
</div>
<div class="button-row">
<button id="onClass" onclick="onClass()" style="width:20% ;">到场</button>
</div>
<div class="button-row">
<button id="correctButton" onclick="repeatCorrectly()" disabled>准确重复问题</button>
<button id="incorrectButton" onclick="repeatIncorrectly()" disabled>重复不准确</button>
</div>
<div class="rating">
<input type="radio" name="rating" id="5" value="5" />
<label for="5" data-value="5"><i class="icon ion-md-star-outline"></i><span class="star-number hidden">5</span></label>
<input type="radio" name="rating" id="4" value="4" />
<label for="4" data-value="4"><i class="icon ion-md-star-outline"></i><span class="star-number hidden">4</span></label>
<input type="radio" name="rating" id="3" value="3" />
<label for="3" data-value="3"><i class="icon ion-md-star-outline"></i><span class="star-number hidden">3</span></label>
<input type="radio" name="rating" id="2" value="2" />
<label for="2" data-value="2"><i class="icon ion-md-star-outline"></i><span class="star-number hidden">2</span></label>
<input type="radio" name="rating" id="1" value="1" />
<label for="1" data-value="1"><i class="icon ion-md-star-outline"></i><span class="star-number hidden">1</span></label>
</div>
<!--<input type="text" id="manualPoints" placeholder="手动加减积分 (范围 0.5 到 3)" /> -->
<div class="button-row">
<!--<button id="manualButton" onclick="updatePoints()" disabled>更新积分</button> -->
<button id="resetButton" onclick="resetAllPoints()">重置所有积分</button>
</div>
<div class="view-student-row">
<button onclick="viewStudentList()">查看学生名单</button>
</div>
</div>
<div id="scrollingNames"></div>
<div id="selectedStudent"></div>
<canvas id="particleCanvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.1/xlsx.full.min.js"></script>
<script>
let students = JSON.parse(localStorage.getItem('students')) || [];
let selectedStudent = null;
let intervalId;
let lock = false;//自动加分锁
let lock1 = false;//手动加分锁
let onClassAdded = false;
let autoPointsAdded = false;
let manualPointsUsed = false;
let studentDrawn = false;
let starLock = false;
const stars = document.querySelectorAll('.rating label');
stars.forEach(star => {
star.addEventListener('mouseenter', () => {
const number = star.querySelector('.star-number');
number.classList.remove('hidden');
});
star.addEventListener('mouseleave', () => {
const number = star.querySelector('.star-number');
number.classList.add('hidden');
});
});
const particleCanvas = document.getElementById('particleCanvas');
const particleCtx = particleCanvas.getContext('2d');
particleCanvas.width = window.innerWidth;
particleCanvas.height = window.innerHeight;
let particles = [];
function createParticlesFromText(a, b) {
// 清空画布
particleCtx.clearRect(0, 0, particleCanvas.width, particleCanvas.height);
// 设置字体
particleCtx.font = '150px "Long Cang"';
particleCtx.fillStyle = '#003366';
// 绘制第一行文字
const x1 = particleCanvas.width / 2 - particleCtx.measureText(selectedStudent.name).width / 2;
const y1 = particleCanvas.height / 2 - 50; // 向上移动50像素
particleCtx.fillText(selectedStudent.name, x1, y1);
// 绘制第二行文字
const x2 = particleCanvas.width / 2 - particleCtx.measureText(selectedStudent.id).width / 2;
const y2 = particleCanvas.height / 2 + 70; // 向下移动70像素
particleCtx.fillText(selectedStudent.id, x2, y2);
// 获取整个画布的像素数据
const imageData = particleCtx.getImageData(0, 0, particleCanvas.width, particleCanvas.height);
// 清空粒子数组
particles = [];
// 创建粒子
for (let y = 0; y < imageData.height; y += 2) {
for (let x = 0; x < imageData.width; x += 2) {
const index = (y * imageData.width + x) * 4;
const alpha = imageData.data[index + 3];
if (alpha > 128) { // 只选择不透明的像素
let particle = new Particle(Math.random() * particleCanvas.width, Math.random() * particleCanvas.height);
particle.targetX = x;
particle.targetY = y;
particles.push(particle);
}
}
}
// 启动粒子动画
animateParticles();
}
function Particle(x, y) {
this.x = x;
this.y = y;
this.size = 1;
this.baseX = this.x;
this.baseY = this.y;
this.density = Math.random() * 30 + 1;
}
Particle.prototype.draw = function () {
particleCtx.fillStyle = 'black';
particleCtx.beginPath();
particleCtx.arc(this.x, this.y, this.size, 0, Math.PI * 2); // 绘制粒子
particleCtx.closePath();
particleCtx.fill();
};
Particle.prototype.update = function () {
let dx = this.targetX - this.x;
let dy = this.targetY - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let speed = distance / 20; // 移动速度,离目标越远速度越快
this.x += dx / distance * speed;
this.y += dy / distance * speed;
// 减少抖动,当粒子非常接近目标位置时停止移动
if (distance < 1) {
this.x = this.targetX;
this.y = this.targetY;
}
};
function animateParticles() {
particleCtx.clearRect(0, 0, particleCanvas.width, particleCanvas.height); // 清除上一帧的粒子
particles.forEach(particle => {
particle.update(); // 更新每个粒子的状态
particle.draw(); // 绘制每个粒子
});
requestAnimationFrame(animateParticles); // 循环动画
}
function showMessage(text) {
const messageDiv = document.getElementById("message");
messageDiv.textContent = text;
messageDiv.style.display = "block"; // 显示消息
setTimeout(() => {
messageDiv.style.display = "none"; // 1秒后隐藏消息
}, 1000);
}
window.onload = function() {
const storedStudent = localStorage.getItem('selectedStudent');//获取selectedStudent
if (storedStudent) {
selectedStudent = JSON.parse(storedStudent);
document.getElementById('selectedStudent').innerText = `${selectedStudent.name} (学号: ${selectedStudent.id})`;
autoPointsAdded = localStorage.getItem('autoPointsAdded') === 'true';
manualPointsUsed = localStorage.getItem('manualPointsUsed') === 'true';
starLock = localStorage.getItem('starLock') === 'true';
onClassAdded = localStorage.getItem('onClassAdded') === 'true';
}
toggleButtons();//按键锁
};
function uploadFile() {
const input = document.getElementById('fileInput');
const file = input.files[0];
if (!file) {
showMessage("请选择一个文件!");
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
// 读取所有行(包括空行)
let allStudents = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
// 如果没有数据,则提示用户
if (allStudents.length < 2) {
showMessage("工作表中没有有效数据!");
return;
}
// 去掉表头
allStudents = allStudents.slice(1);
// 过滤有效行,确保每行的有效性
students = allStudents
.filter(row => row[0] !== undefined && row[0] !== null && row[0] !== '') // 确保 ID 不为空
.map(row => ({
id: row[0],
name: row[1], // 假设第二列为名字
points: 0 // 默认分数为 0
}));
// 如果没有有效的学生数据,则提示
if (students.length === 0) {
showMessage("工作表中没有有效学生数据!");
return;
}
localStorage.setItem('students', JSON.stringify(students));
showMessage("学生名单导入成功!");
toggleButtons();
};
reader.readAsArrayBuffer(file);
}
function updateFileName() {
const input = document.getElementById('fileInput');
const fileName = document.getElementById('fileName');
if (input.files.length > 0) {
fileName.innerText = input.files[0].name; // 显示选择的文件名
} else {
fileName.innerText = '未选择文件'; // 显示未选择文件的提示
}
}
function toggleButtons() {
const buttons = ['drawButton', 'correctButton', 'incorrectButton', 'manualButton'];
const enable = students.length > 0;
buttons.forEach(buttonId => {
document.getElementById(buttonId).disabled = !enable;
});
}
function viewStudentList() {
window.location.href = 'studentList.html';
}
function startDraw() {
if (students.length === 0) {
showMessage("请先导入学生名单!");
return;
}
// 隐藏页面元素
document.querySelector('.container').classList.add('hidden');
// 隐藏“开始点名”按钮
//document.getElementById('drawButton').style.display = 'none';
lock = false;
lock1 = false;
onClassAdded = false;
autoPointsAdded = false;
manualPointsUsed = false;
starLock = false;
const totalPoints = students.reduce((sum, student) => sum + (student.points > 10 ? 1 / student.points : 10 - student.points), 0);
const randomValue = Math.random() * totalPoints;
let cumulativePoints = 0;
for (const student of students) {
cumulativePoints += Math.max(1, 10 - student.points);
if (randomValue < cumulativePoints) {
selectedStudent = student;
break;
}
}
setTimeout(scrollStudents, 350);
}
function onClass() {
if(!onClassAdded && selectedStudent) {
selectedStudent.points += 1;
synPoints();
onClassAdded = true;
updateLocalStorage();
showMessage(`${selectedStudent.name}到场,加分 1。`);
} else if (!selectedStudent) {
showMessage("请先抽取一位学生");
} else {
showMessage("到场已加分");
}
}
document.addEventListener('click', async function(event) {
const drawButton = document.getElementById('drawButton');
const scrollingNamesDiv = document.getElementById('scrollingNames');
const selectedStudentDiv = document.getElementById('selectedStudent');
if(!clickEnabled){
return;
}
if (!drawButton.contains(event.target) &&
!scrollingNamesDiv.contains(event.target) &&
!selectedStudentDiv.contains(event.target)) {
// 清空显示的被点到学生信息
scrollingNamesDiv.innerHTML = '';
particles = [];
particleCtx.clearRect(0, 0, particleCanvas.width, particleCanvas.height);
// 恢复页面元素
document.querySelector('.container').classList.remove('hidden');
selectedStudentDiv.style.display = 'block'; // 显示
}
});
function synPoints() {
// 更新 students 数组中的相应学生积分
const studentIndex = students.findIndex(s => s.id === selectedStudent.id);
if (studentIndex !== -1) {
students[studentIndex].points = selectedStudent.points; // 确保 students 数组中的积分也更新
}
}
function repeatCorrectly() {
if (!lock && selectedStudent && !autoPointsAdded) {
selectedStudent.points += 0.5;
synPoints();
autoPointsAdded = true;
lock = true;
updateLocalStorage();
showMessage(`已为 ${selectedStudent.name}加分 0.5。`);
} else if (!selectedStudent) {
showMessage("请先抽取一位学生");
} else {
showMessage("已为该学生加过分");
}
}
function repeatIncorrectly() {
if (!lock && selectedStudent && !autoPointsAdded) {
selectedStudent.points -= 1;
synPoints();
autoPointsAdded = true;
lock = true;
updateLocalStorage();
showMessage(`已为 ${selectedStudent.name} 扣分 1。`);
} else if (!selectedStudent) {
showMessage("请先抽取一位学生");
} else {
showMessage("已为该学生加过分");
}
}
let clickEnabled = true; // 用于跟踪点击是否被允许
function scrollStudents() {
document.getElementById('selectedStudent').innerText = '';
const scrollingNamesDiv = document.getElementById('scrollingNames');
scrollingNamesDiv.innerHTML = '';
let index = 0;
clickEnabled = false; // 禁用点击
return new Promise((resolve) => {
intervalId = setInterval(() => {
scrollingNamesDiv.innerText = students[index].name;
index = (index + 1) % students.length;
}, 100);
setTimeout(() => {
clearInterval(intervalId);
scrollingNamesDiv.innerHTML = '';
if (selectedStudent) {
document.getElementById('selectedStudent').innerText = `${selectedStudent.name} (学号: ${selectedStudent.id})`;
document.getElementById('selectedStudent').style.display = 'none'; // 隐藏
createParticlesFromText(`${selectedStudent.name} (学号:${selectedStudent.id})`);
updateLocalStorage();
}
resolve(); // 完成滚动时解析Promise
clickEnabled = true; // 恢复点击
}, 3000);
});
}
function updateLocalStorage() {
localStorage.setItem('selectedStudent', JSON.stringify(selectedStudent));
localStorage.setItem('students', JSON.stringify(students));
localStorage.setItem('autoPointsAdded', autoPointsAdded);
localStorage.setItem('manualPointsUsed', manualPointsUsed);
localStorage.setItem('starLock',starLock);
localStorage.setItem('onClassAdded',onClassAdded);
}
// 初始化星级点击事件监听器
document.querySelectorAll('.rating input').forEach(star => {
star.addEventListener('change', function() {
if (!starLock) { // 检查星星是否已经被点击过
updatePoints(this.value);
} else {
showMessage("每次点名只能点击一次星星进行加分。");
}
});
});
// 根据选中的星级为学生加分
function updatePoints(points) {
points = parseInt(points); // 将点击的星星值转换为整数
if (selectedStudent) {
if (!starLock) { // 检查星星是否已经被点击过
selectedStudent.points += points; // 根据星星值加分
synPoints();
showMessage(`已为 ${selectedStudent.name} 加分 ${points}。`);
starLock = true; // 点击后锁定星星
updateLocalStorage();
} else {
showMessage("每次点名只能点击一次星星进行加分。");
}
} else {
showMessage("请先点名一个学生。");
}
}
// 禁用所有星星选择
function disableStars() {
document.querySelectorAll('.rating input').forEach(star => {
star.disabled = true;
});
}
// 启用所有星星选择
function enableStars() {
document.querySelectorAll('.rating input').forEach(star => {
star.disabled = false;
});
}
function resetAllPoints() {
students.forEach(student => student.points = 0);
localStorage.setItem('students', JSON.stringify(students));
localStorage.removeItem('selectedStudent');
selectedStudent = null;
document.getElementById('selectedStudent').innerText = '';
showMessage("所有积分已重置!");
}
</script>
</body>
</html>