代码功能
这个是一个HTML网页端,简单来说就是可以双击之后运行进行点名。
当然,不局限于课堂点名
代码功能
Excel 导入增强:
增加了列选择器,可以指定从哪一列读取学生姓名
增加了起始行选择器,可以跳过标题行或其他非学生数据行
自动检测功能:尝试识别可能包含姓名的列并自动选择
一键清空功能:
在学生列表上方添加了 "清空名单" 按钮
点击后会提示确认,防止误操作
用户体验优化:
导入 Excel 后显示导入选项,导入完成后自动隐藏
优化了通知提示效果
保持了原有随机点名的核心功能不变
界面截图

源码
cpp
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>阿幸随机点名抽问</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#4F46E5',
secondary: '#EC4899',
accent: '#8B5CF6',
neutral: '#F3F4F6',
"neutral-dark": '#1F2937',
},
fontFamily: {
cute: ['"Comic Sans MS"', '"Marker Felt"', 'Arial', 'sans-serif'],
},
animation: {
'bounce-slow': 'bounce 3s infinite',
}
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.text-shadow {
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.bg-cute {
background-image: radial-gradient(circle at 25% 25%, rgba(255, 220, 220, 0.8) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(220, 220, 255, 0.8) 0%, transparent 50%);
background-color: #FFF5F7;
}
.name-container {
perspective: 1000px;
}
.name-card {
transform-style: preserve-3d;
transition: transform 0.5s;
}
.name-card:hover {
transform: translateY(-5px) rotateX(5deg);
}
}
</style>
</head>
<body class="min-h-screen bg-cute flex flex-col items-center justify-between p-4 md:p-8">
<!-- 顶部标题区域 -->
<header class="w-full text-center mb-8">
<h1 class="text-[clamp(2rem,5vw,3.5rem)] font-cute font-bold text-primary text-shadow animate-bounce-slow">
<i class="fa fa-star text-yellow-400"></i>
阿幸随机点名抽问
<i class="fa fa-star text-yellow-400"></i>
</h1>
<p class="text-gray-600 mt-2">公平、随机、有趣的课堂互动工具</p>
</header>
<!-- 主要内容区域 -->
<main class="flex-1 w-full max-w-4xl flex flex-col items-center justify-center">
<!-- 名字显示区域 -->
<div class="name-container w-full bg-white rounded-2xl shadow-lg p-6 md:p-10 mb-8 transform transition-all duration-300 hover:shadow-xl">
<div class="name-card bg-gradient-to-br from-pink-100 to-purple-100 rounded-xl p-8 text-center">
<p id="displayedName" class="text-[clamp(2.5rem,8vw,5rem)] font-black text-neutral-dark transition-all duration-500">
准备开始
</p>
</div>
</div>
<!-- 控制面板 -->
<div class="w-full bg-white rounded-xl shadow-md p-6 md:p-8">
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
<!-- 速度控制滑块 -->
<div class="w-full md:w-1/3">
<label for="speedControl" class="block text-gray-700 font-medium mb-2 flex items-center">
<i class="fa fa-tachometer text-accent mr-2"></i>
滚动速度
</label>
<input type="range" id="speedControl" min="50" max="500" value="200"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>慢</span>
<span>快</span>
</div>
</div>
<!-- 开始/停止按钮 -->
<div class="w-full md:w-auto flex justify-center md:justify-end">
<button id="toggleButton" class="bg-primary hover:bg-primary/90 text-white font-bold py-3 px-8 rounded-full shadow-lg transform transition-all duration-300 hover:scale-105 active:scale-95 flex items-center">
<i class="fa fa-play mr-2"></i>
<span>开始点名</span>
</button>
</div>
</div>
</div>
<!-- 学生名单管理 -->
<div class="w-full mt-8 bg-white rounded-xl shadow-md p-6 md:p-8">
<h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center">
<i class="fa fa-users text-secondary mr-2"></i>
学生名单管理
</h2>
<div class="flex flex-col md:flex-row gap-4">
<!-- 文本导入 -->
<div class="w-full md:w-1/2">
<label for="nameList" class="block text-gray-700 font-medium mb-2">
输入学生姓名(用逗号、空格或换行分隔)
</label>
<textarea id="nameList" rows="4" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all" placeholder="张三, 李四, 王五..."></textarea>
<button id="importNames" class="mt-2 bg-accent hover:bg-accent/90 text-white font-medium py-2 px-4 rounded-lg shadow transition-all duration-200 flex items-center">
<i class="fa fa-upload mr-1"></i> 导入名单
</button>
</div>
<!-- Excel导入 -->
<div class="w-full md:w-1/2">
<label class="block text-gray-700 font-medium mb-2">
或从Excel文件导入
</label>
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-primary transition-colors cursor-pointer" id="excelDropArea">
<input type="file" id="excelFile" accept=".xlsx,.xls" class="hidden">
<i class="fa fa-file-excel-o text-3xl text-gray-400 mb-2"></i>
<p class="text-gray-500">拖放Excel文件到此处<br>或点击选择文件</p>
</div>
<div id="excelFileName" class="mt-2 text-sm text-gray-600 hidden"></div>
<!-- Excel导入选项 -->
<div id="excelOptions" class="mt-4 grid grid-cols-2 gap-4 hidden">
<div>
<label for="excelColumn" class="block text-gray-700 text-sm font-medium mb-1">
姓名列选择
</label>
<select id="excelColumn" class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary">
<option value="A">A列</option>
<option value="B">B列</option>
<option value="C">C列</option>
<option value="D">D列</option>
<option value="E">E列</option>
<option value="F">F列</option>
</select>
</div>
<div>
<label for="excelStartRow" class="block text-gray-700 text-sm font-medium mb-1">
起始行号
</label>
<input type="number" id="excelStartRow" min="1" value="1"
class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary">
</div>
</div>
<button id="importExcel" class="mt-2 bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg shadow transition-all duration-200 flex items-center hidden">
<i class="fa fa-check mr-1"></i> 确认导入Excel
</button>
</div>
</div>
<!-- 学生列表显示 -->
<div class="mt-6">
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-medium text-gray-700 flex items-center">
<i class="fa fa-list-ul text-primary mr-2"></i>
已导入学生 (<span id="studentCount">0</span>)
</h3>
<button id="clearStudents" class="text-red-500 hover:text-red-600 text-sm font-medium flex items-center">
<i class="fa fa-trash mr-1"></i> 清空名单
</button>
</div>
<div id="studentList" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2 max-h-40 overflow-y-auto p-2 bg-gray-50 rounded-lg">
<div class="text-gray-400 col-span-full text-center py-4">
暂无学生名单
</div>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="w-full text-center text-gray-500 text-sm mt-8">
<p>双击此页面即可离线使用 | 点击右上角可保存为应用</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM元素
const displayedName = document.getElementById('displayedName');
const toggleButton = document.getElementById('toggleButton');
const speedControl = document.getElementById('speedControl');
const nameList = document.getElementById('nameList');
const importNames = document.getElementById('importNames');
const excelFile = document.getElementById('excelFile');
const excelDropArea = document.getElementById('excelDropArea');
const excelFileName = document.getElementById('excelFileName');
const excelOptions = document.getElementById('excelOptions');
const importExcel = document.getElementById('importExcel');
const excelColumn = document.getElementById('excelColumn');
const excelStartRow = document.getElementById('excelStartRow');
const studentList = document.getElementById('studentList');
const studentCount = document.getElementById('studentCount');
const clearStudents = document.getElementById('clearStudents');
// 状态变量
let students = [];
let isRolling = false;
let rollInterval;
let rollSpeed = 200; // 默认速度,值越小越快
let selectedName = null;
let excelData = null;
// 处理学生名单导入
importNames.addEventListener('click', function() {
const text = nameList.value.trim();
if (!text) {
alert('请输入学生姓名');
return;
}
// 支持逗号、空格、换行等多种分隔符
students = text.split(/[,\s\n]+/).filter(name => name.trim() !== '');
updateStudentList();
resetRollState();
showNotification(`成功导入 ${students.length} 名学生`);
});
// 处理Excel文件导入
excelFile.addEventListener('change', handleExcelFile);
excelDropArea.addEventListener('click', () => excelFile.click());
// 拖放功能
excelDropArea.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('border-primary', 'bg-primary/5');
});
excelDropArea.addEventListener('dragleave', function() {
this.classList.remove('border-primary', 'bg-primary/5');
});
excelDropArea.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('border-primary', 'bg-primary/5');
if (e.dataTransfer.files.length) {
excelFile.files = e.dataTransfer.files;
handleExcelFile();
}
});
function handleExcelFile() {
const file = excelFile.files[0];
if (!file) return;
if (!file.name.match(/\.(xlsx|xls)$/i)) {
alert('请选择Excel文件 (.xlsx 或 .xls)');
return;
}
excelFileName.textContent = `已选择: ${file.name}`;
excelFileName.classList.remove('hidden');
excelOptions.classList.remove('hidden');
importExcel.classList.remove('hidden');
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// 保存Excel数据供后续处理
excelData = worksheet;
// 尝试自动检测姓名列
autoDetectNameColumn(worksheet);
} catch (error) {
console.error('Excel处理错误:', error);
alert('处理Excel文件时出错,请确保文件格式正确');
}
};
reader.readAsArrayBuffer(file);
}
// 自动检测姓名列
function autoDetectNameColumn(worksheet) {
const nameKeywords = ['姓名', '名字', '学生姓名', 'name', 'student'];
const headerRow = XLSX.utils.sheet_to_json(worksheet, { header: 1, range: 0, raw: false })[0] || [];
for (let i = 0; i < headerRow.length; i++) {
const cellValue = headerRow[i] || '';
const cellValueLower = cellValue.toString().toLowerCase();
if (nameKeywords.some(kw => cellValueLower.includes(kw))) {
// 将列索引转换为字母 (0->A, 1->B, ...)
const columnLetter = String.fromCharCode(65 + i);
excelColumn.value = columnLetter;
return;
}
}
// 默认选择A列
excelColumn.value = 'A';
}
// 确认导入Excel
importExcel.addEventListener('click', function() {
if (!excelData) {
alert('请先选择Excel文件');
return;
}
const column = excelColumn.value;
const startRow = parseInt(excelStartRow.value) || 1;
try {
// 提取指定列的数据
const range = XLSX.utils.decode_range(excelData['!ref'] || 'A1:A1');
const maxRow = range.e.r;
students = [];
for (let row = startRow; row <= maxRow; row++) {
const cellAddress = `${column}${row}`;
const cell = excelData[cellAddress];
if (cell && cell.v && cell.v.toString().trim() !== '') {
students.push(cell.v.toString().trim());
}
}
if (students.length === 0) {
alert('未能从Excel文件中提取到有效姓名,请检查列选择和起始行');
return;
}
updateStudentList();
resetRollState();
showNotification(`成功从Excel导入 ${students.length} 名学生`);
// 导入后隐藏选项
excelOptions.classList.add('hidden');
importExcel.classList.add('hidden');
} catch (error) {
console.error('Excel导入错误:', error);
alert('导入Excel数据时出错,请检查设置');
}
});
// 更新学生列表显示
function updateStudentList() {
studentList.innerHTML = '';
if (students.length === 0) {
studentList.innerHTML = `
<div class="text-gray-400 col-span-full text-center py-4">
暂无学生名单
</div>
`;
studentCount.textContent = '0';
return;
}
students.forEach(name => {
const nameEl = document.createElement('div');
nameEl.className = 'bg-white border border-gray-200 rounded p-2 text-center text-sm hover:bg-primary/5 transition-colors';
nameEl.textContent = name;
nameEl.dataset.name = name;
studentList.appendChild(nameEl);
});
studentCount.textContent = students.length;
}
// 开始/停止点名
toggleButton.addEventListener('click', function() {
if (students.length === 0) {
alert('请先导入学生名单');
return;
}
if (isRolling) {
stopRolling();
} else {
startRolling();
}
});
// 开始滚动
function startRolling() {
isRolling = true;
toggleButton.innerHTML = '<i class="fa fa-stop mr-2"></i><span>停止点名</span>';
toggleButton.classList.remove('bg-primary');
toggleButton.classList.add('bg-red-500', 'hover:bg-red-600');
// 重置之前的选中状态
displayedName.classList.remove('text-primary', 'font-black', 'scale-110');
// 开始滚动
rollInterval = setInterval(() => {
const randomIndex = Math.floor(Math.random() * students.length);
displayedName.textContent = students[randomIndex];
// 添加动画效果
displayedName.classList.add('animate-pulse');
setTimeout(() => {
displayedName.classList.remove('animate-pulse');
}, 50);
}, 600 - rollSpeed); // 反转速度值,使滑块右侧表示更快
}
// 停止滚动
function stopRolling() {
clearInterval(rollInterval);
isRolling = false;
toggleButton.innerHTML = '<i class="fa fa-play mr-2"></i><span>开始点名</span>';
toggleButton.classList.remove('bg-red-500', 'hover:bg-red-600');
toggleButton.classList.add('bg-primary', 'hover:bg-primary/90');
// 突出显示选中的名字
selectedName = displayedName.textContent;
displayedName.classList.add('text-primary', 'font-black', 'scale-110', 'transition-transform', 'duration-500');
// 高亮学生列表中的选中项
highlightSelectedStudent();
}
// 高亮选中的学生
function highlightSelectedStudent() {
const nameElements = studentList.querySelectorAll('[data-name]');
nameElements.forEach(el => {
if (el.dataset.name === selectedName) {
el.classList.add('bg-primary/10', 'border-primary', 'font-bold');
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
el.classList.remove('bg-primary/10', 'border-primary', 'font-bold');
}
});
}
// 重置滚动状态
function resetRollState() {
if (isRolling) {
clearInterval(rollInterval);
isRolling = false;
}
displayedName.textContent = '准备开始';
displayedName.classList.remove('text-primary', 'font-black', 'scale-110');
toggleButton.innerHTML = '<i class="fa fa-play mr-2"></i><span>开始点名</span>';
toggleButton.classList.remove('bg-red-500', 'hover:bg-red-600');
toggleButton.classList.add('bg-primary', 'hover:bg-primary/90');
// 清除学生列表中的高亮
const nameElements = studentList.querySelectorAll('[data-name]');
nameElements.forEach(el => {
el.classList.remove('bg-primary/10', 'border-primary', 'font-bold');
});
}
// 速度控制
speedControl.addEventListener('input', function() {
rollSpeed = parseInt(this.value);
// 如果正在滚动,更新速度
if (isRolling) {
clearInterval(rollInterval);
startRolling();
}
});
// 显示通知
function showNotification(message) {
// 创建通知元素
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg transform transition-all duration-300 translate-x-full opacity-0 z-50';
notification.textContent = message;
// 添加到页面
document.body.appendChild(notification);
// 显示通知
setTimeout(() => {
notification.classList.remove('translate-x-full', 'opacity-0');
}, 10);
// 自动隐藏
setTimeout(() => {
notification.classList.add('translate-x-full', 'opacity-0');
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// 清空学生名单
clearStudents.addEventListener('click', function() {
if (students.length === 0) return;
if (confirm('确定要清空当前学生名单吗?')) {
students = [];
updateStudentList();
resetRollState();
showNotification('已清空学生名单');
}
});
// 键盘快捷键
document.addEventListener('keydown', function(e) {
// 空格键控制开始/停止
if (e.code === 'Space') {
e.preventDefault();
if (students.length > 0) {
toggleButton.click();
}
}
});
// 示例数据
nameList.value = "张三、李四、王五、赵六、钱七、孙八、周九、吴十、郑十一、王十二";
});
</script>
</body>
</html>