python 自动化从入门到实战-开发一个随机点名系统(6)
这款超酷的随机点名系统,让课堂互动更有趣!
你是否遇到过这样的场景:课堂点名时,每次都点固定几个同学,其他同学逐渐失去参与感?或者在会议讨论时,想让每个人都有发言机会,却不知道先叫谁?今天,我要给大家推荐一个超实用、超炫酷的工具——科技感随机点名系统!
不仅如此,我还会为大家详细解析这个系统的源代码,让你不仅会用,还能了解它是如何工作的,甚至可以自己动手修改和扩展功能!
为什么需要随机点名系统?
无论是老师上课提问、公司团队游戏,还是活动抽奖环节,随机点名都是一个公平又有趣的方式。它能让每个人都有平等的参与机会,也能为场景增添一些紧张和惊喜感。而今天介绍的这款系统,不仅功能实用,界面更是科技感十足,让点名过程变得像科幻电影一样炫酷!
这个系统到底有多酷?
🎮 科技感十足的界面设计
一打开系统,你就会被它的外观吸引!深蓝色的背景搭配霓虹色调,仿佛置身于未来科技世界。界面上的各种元素都带有平滑的动画效果,让整个点名过程充满动感和视觉冲击力。
🚀 流畅的动画效果
当你点击"开始点名"按钮时,系统会以流畅的动画效果快速切换显示不同的名字,速度由慢到快再到慢,模拟真实抽奖的紧张感。停止时还会有特别的强调效果,让最终选中的人成为焦点。
💾 数据自动保存,下次打开直接用
最贴心的是,你设置的人员名单会自动保存到电脑里。下次再打开系统,之前添加的所有人名都还在,完全不需要重新输入,省心又省力!
📱 手机电脑都能用
不管你是在教室里用大屏幕投影,还是在办公室用电脑,甚至是用手机临时组织活动,这个系统都能完美适配,让你随时随地都能使用。
📋 自动记录历史,不怕忘记
系统会自动保存最近10次的点名结果,这样你就能清楚地看到谁已经被点过名,避免重复提问同一个人。
只需3步,轻松上手使用
这个系统操作超级简单,即使你是电脑小白也能轻松学会!
第一步:安装准备
首先,确保你的电脑上已经安装了Python(版本3.7或更高)。如果没有安装,可以在Python官网下载安装包,按照提示一步步安装即可。
第二步:启动系统
找到下载好的项目文件,双击运行random_caller.py
文件。或者,打开命令提示符(Windows用户)或终端(Mac用户),进入项目文件夹,输入以下命令:
python random_caller.py
启动成功后,打开浏览器,在地址栏输入http://localhost:5000/
,就能看到炫酷的随机点名系统界面了!
第三步:开始使用
-
设置人员名单:点击"设置人员"按钮,在弹出的窗口中输入每个人的姓名,点击"添加"按钮。如果想删除某个人,点击旁边的"删除"按钮即可。设置完成后,点击"保存设置"。
-
开始点名:确保已经添加了人员名单后,点击"开始点名"按钮,系统会开始随机滚动显示姓名。想停止时,点击"停止点名"按钮,系统会显示最终选中的人。
-
查看历史记录:页面下方会自动显示最近10次的点名结果,方便你查看。
常见问题解答
Q:为什么打开后看不到任何人名?
A:第一次使用时,系统会自动添加一些示例人员。如果没有显示,点击"设置人员"按钮手动添加即可。
Q:设置的人员名单保存在哪里?
A:保存在程序文件夹里的person.txt
文件中,即使关闭程序也不会丢失。
Q:页面显示不正常怎么办?
A:试试刷新页面或清除浏览器缓存,一般就能解决问题。
Q:可以在手机上使用吗?
A:当然可以!系统支持响应式设计,在手机上打开同样的网址就能使用。
代码解析:看看这个系统是如何工作的
现在,让我们来揭开这个随机点名系统的神秘面纱!虽然听起来很高大上,但它的核心代码其实非常简单易懂。
1. 程序的骨架
random_caller.py
是整个系统的核心文件,它就像一个指挥中心,负责处理所有的请求和数据存储。我们先看看它的基本结构:
from flask import Flask, render_template, request, jsonify
import random
import osapp = Flask(__name__)# 各种功能函数和API接口定义...if __name__ == '__main__':app.run(debug=True)
这就是一个标准的Flask Web应用结构。简单来说,它导入了必要的库,创建了一个Flask应用实例,然后定义了各种功能,最后在主程序中启动了这个应用。
2. 数据存储:如何保存人员名单
系统使用一个简单的文本文件person.txt
来保存人员名单。程序启动时,会检查这个文件是否存在,如果不存在,就创建一个并添加一些默认人员:
# 确保保存人员名单的文件存在
PERSON_FILE = 'person.txt'
if not os.path.exists(PERSON_FILE):with open(PERSON_FILE, 'w', encoding='utf-8') as f:# 初始添加一些示例人员default_persons = ['张三', '李四', '王五', '赵六', '钱七']f.write('\n'.join(default_persons))
这里使用了Python的文件操作功能,非常基础但很实用!
3. 核心功能函数
系统有几个核心功能函数,它们就像小助手一样,分别负责不同的任务:
读取人员名单
def read_persons():try:with open(PERSON_FILE, 'r', encoding='utf-8') as f:persons = [line.strip() for line in f if line.strip()]return personsexcept Exception as e:print(f"读取人员名单失败: {e}")return []
这个函数的作用是从文件中读取所有人员的名字,并将它们整理成一个列表返回。
保存人员名单
def save_persons(persons):try:with open(PERSON_FILE, 'w', encoding='utf-8') as f:f.write('\n'.join(persons))return Trueexcept Exception as e:print(f"保存人员名单失败: {e}")return False
这个函数将修改后的人员名单写回到文件中,实现数据的持久化保存。
随机选择人员
def random_select(persons):if not persons:return Nonereturn random.choice(persons)
这个函数是随机点名的核心,它使用Python的random库,从人员列表中随机选择一个名字返回。
4. API接口:前后端通信的桥梁
系统定义了多个API接口,用于前端页面和后端程序之间的通信。这些接口就像一扇扇门,前端通过它们向后端发送请求并获取数据。
首页接口
@app.route('/')
def index():persons = read_persons()return render_template('random_caller.html', persons=persons)
当你访问系统首页时,这个接口会被调用,它读取人员名单,然后渲染并返回前端页面。
获取人员名单接口
@app.route('/get_persons')
def get_persons():persons = read_persons()return jsonify(persons=persons)
这个接口用于获取最新的人员名单,前端页面加载后会调用它来显示当前的人员列表。
随机点名接口
@app.route('/random_call')
def random_call():persons = read_persons()if not persons:return jsonify(success=False, message='请先添加人员名单')selected = random_select(persons)return jsonify(success=True, person=selected)
这个接口是随机点名功能的核心,当你点击"开始点名"按钮时,前端会调用它来获取一个随机选中的人员名字。
人员管理接口
系统还提供了添加和删除人员的接口:
@app.route('/add_person', methods=['POST'])
def add_person():data = request.jsonperson = data.get('person', '')# 添加人员的逻辑...@app.route('/remove_person', methods=['POST'])
def remove_person():data = request.jsonperson = data.get('person', '')# 删除人员的逻辑...
这些接口让你可以在前端页面上方便地管理人员名单。
5. 程序入口
最后,程序的入口部分非常简单:
if __name__ == '__main__':app.run(debug=True)
这行代码的作用是,当你直接运行这个Python文件时,启动Flask应用服务器,并开启调试模式,这样你就能在浏览器中访问这个系统了。
让课堂和活动更有趣
无论是老师用来提问学生,还是企业用来做团队游戏,甚至是朋友聚会时玩抽奖游戏,这个随机点名系统都能派上用场。它不仅公平公正,还能为活动增添不少乐趣和科技感。
现在就下载这个系统,让你的课堂、会议或活动变得更加生动有趣吧!
自己动手,让系统更强大!
如果你对编程有兴趣,还可以尝试自己动手修改和扩展这个系统!这里有一些简单的扩展方向:
1. 添加更多炫酷效果
你可以在前端HTML文件中添加更多的CSS动画效果,让点名过程更加炫酷。比如添加粒子效果、3D旋转效果等。
2. 增加音效
在random_caller.html
文件中,有两个已经预留好的函数:playRollingSound
和playEndSound
,你可以添加音效文件,让系统在开始点名和停止点名时播放不同的声音。
3. 导入导出功能
你可以扩展后端代码,添加导入导出人员名单的功能,支持从Excel或CSV文件导入人员名单,也可以将当前的人员名单导出保存。
4. 添加权重设置
如果需要让某些人有更高的被选中概率,你可以给每个人设置一个权重值,修改随机选择的算法。
学习编程的好例子
这个随机点名系统虽然简单,但它涵盖了很多编程的基础知识:
- 后端开发:使用Python和Flask框架
- 前端开发:HTML、CSS和JavaScript
- 数据存储:文件操作和本地存储
- API设计:前后端通信接口
对于编程初学者来说,这是一个非常好的学习项目。你可以先运行它,然后逐步修改代码,观察变化,从中学习编程的基本概念和方法。
完整代码:
1、后端代码
from flask import Flask, render_template, request, jsonify, send_file
import random
import os
import json
import time
from datetime import datetimeapp = Flask(__name__)# 确保保存人员名单的文件存在
PERSON_FILE = 'person.txt'
if not os.path.exists(PERSON_FILE):with open(PERSON_FILE, 'w', encoding='utf-8') as f:# 初始添加一些示例人员default_persons = ['张三', '李四', '王五', '赵六', '钱七']f.write('\n'.join(default_persons))# 读取人员名单
def read_persons():try:with open(PERSON_FILE, 'r', encoding='utf-8') as f:persons = [line.strip() for line in f if line.strip()]return personsexcept Exception as e:print(f"读取人员名单失败: {e}")return []# 保存人员名单
def save_persons(persons):try:with open(PERSON_FILE, 'w', encoding='utf-8') as f:f.write('\n'.join(persons))return Trueexcept Exception as e:print(f"保存人员名单失败: {e}")return False# 随机选择一个人员
def random_select(persons):if not persons:return Nonereturn random.choice(persons)@app.route('/')
def index():persons = read_persons()return render_template('random_caller.html', persons=persons)@app.route('/get_persons')
def get_persons():persons = read_persons()return jsonify(persons=persons)@app.route('/save_persons', methods=['POST'])
def save_persons_api():data = request.jsonpersons = data.get('persons', [])if save_persons(persons):return jsonify(success=True, message='保存成功')else:return jsonify(success=False, message='保存失败')@app.route('/random_call')
def random_call():persons = read_persons()if not persons:return jsonify(success=False, message='请先添加人员名单')selected = random_select(persons)return jsonify(success=True, person=selected)@app.route('/add_person', methods=['POST'])
def add_person():data = request.jsonperson = data.get('person', '')if not person:return jsonify(success=False, message='请输入人员姓名')persons = read_persons()if person in persons:return jsonify(success=False, message='该人员已存在')persons.append(person)if save_persons(persons):return jsonify(success=True, message='添加成功', persons=persons)else:return jsonify(success=False, message='添加失败')@app.route('/remove_person', methods=['POST'])
def remove_person():data = request.jsonperson = data.get('person', '')if not person:return jsonify(success=False, message='请选择要删除的人员')persons = read_persons()if person not in persons:return jsonify(success=False, message='该人员不存在')persons.remove(person)if save_persons(persons):return jsonify(success=True, message='删除成功', persons=persons)else:return jsonify(success=False, message='删除失败')if __name__ == '__main__':app.run(debug=True)
2、前端代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>随机点名系统</title><style>@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&family=Rajdhani:wght@300;400;500;600;700&display=swap');:root {--primary-color: #00e5ff;--secondary-color: #ffea00;--bg-color: #050b18;--card-bg: rgba(14, 22, 48, 0.7);--text-color: #ffffff;--border-color: rgba(0, 229, 255, 0.3);}* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Rajdhani', sans-serif;background-color: var(--bg-color);color: var(--text-color);background-image: radial-gradient(circle at 10% 20%, rgba(0, 229, 255, 0.1) 0%, transparent 20%),radial-gradient(circle at 80% 30%, rgba(255, 234, 0, 0.1) 0%, transparent 20%),radial-gradient(circle at 40% 70%, rgba(0, 229, 255, 0.05) 0%, transparent 15%),radial-gradient(circle at 90% 90%, rgba(255, 234, 0, 0.05) 0%, transparent 15%);min-height: 100vh;display: flex;flex-direction: column;overflow-x: hidden;position: relative;}body::before {content: '';position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: linear-gradient(rgba(5, 11, 24, 0.95), rgba(5, 11, 24, 0.95)),url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%2300e5ff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");z-index: -1;}.container {flex: 1;max-width: 1200px;margin: 0 auto;padding: 20px;display: flex;flex-direction: column;align-items: center;}header {text-align: center;margin-bottom: 40px;position: relative;padding: 20px;}h1 {font-family: 'Orbitron', sans-serif;font-size: 3rem;font-weight: 700;margin-bottom: 10px;color: var(--primary-color);text-shadow: 0 0 15px rgba(0, 229, 255, 0.5), 0 0 30px rgba(0, 229, 255, 0.3);letter-spacing: 2px;position: relative;display: inline-block;}h1::before, h1::after {content: '';position: absolute;width: 30%;height: 1px;background: linear-gradient(to right, transparent, var(--primary-color), transparent);top: 50%;}h1::before {left: -40%;}h1::after {right: -40%;}.subtitle {font-size: 1.2rem;color: rgba(255, 255, 255, 0.7);margin-bottom: 20px;}.main-content {display: flex;flex-direction: column;align-items: center;width: 100%;max-width: 800px;}.caller-box {width: 100%;background: var(--card-bg);border-radius: 10px;padding: 40px;margin-bottom: 30px;border: 1px solid var(--border-color);box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 10px rgba(0, 229, 255, 0.1);position: relative;overflow: hidden;}.caller-box::before {content: '';position: absolute;top: -5px;left: -5px;right: -5px;height: 5px;background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--primary-color));animation: gradientFlow 5s linear infinite;}@keyframes gradientFlow {0% { background-position: 0% 50%; }100% { background-position: 200% 50%; }}.display-area {width: 100%;height: 300px;display: flex;justify-content: center;align-items: center;position: relative;margin-bottom: 30px;perspective: 1000px;}.person-display {font-family: 'Orbitron', sans-serif;font-size: 5rem;font-weight: 700;color: var(--secondary-color);text-align: center;text-shadow: 0 0 20px rgba(255, 234, 0, 0.6), 0 0 40px rgba(255, 234, 0, 0.3);transition: all 0.3s ease;transform-style: preserve-3d;min-height: 150px;display: flex;align-items: center;justify-content: center;}.person-display.rolling {animation: rolling 0.1s linear infinite;}@keyframes rolling {0% { transform: translateY(0) rotateX(0); }50% { transform: translateY(-10px) rotateX(5deg); }100% { transform: translateY(0) rotateX(0); }}.controls {display: flex;justify-content: center;gap: 20px;flex-wrap: wrap;}button {font-family: 'Orbitron', sans-serif;padding: 12px 30px;font-size: 1.1rem;font-weight: 600;border: none;border-radius: 5px;cursor: pointer;transition: all 0.3s ease;position: relative;overflow: hidden;letter-spacing: 1px;background: linear-gradient(135deg, var(--primary-color), #00897b);color: white;box-shadow: 0 4px 15px rgba(0, 229, 255, 0.3);}button:hover {transform: translateY(-3px);box-shadow: 0 6px 20px rgba(0, 229, 255, 0.4);}button:active {transform: translateY(0);}button::before {content: '';position: absolute;top: 0;left: -100%;width: 100%;height: 100%;background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);transition: all 0.5s;}button:hover::before {left: 100%;}.secondary-btn {background: linear-gradient(135deg, #6d4c41, #4e342e);box-shadow: 0 4px 15px rgba(109, 76, 65, 0.3);}.secondary-btn:hover {box-shadow: 0 6px 20px rgba(109, 76, 65, 0.4);}.status-info {position: fixed;top: 20px;right: 20px;background: var(--card-bg);padding: 15px 20px;border-radius: 5px;border-left: 4px solid var(--primary-color);box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);opacity: 0;transform: translateX(100px);transition: all 0.5s ease;z-index: 1000;}.status-info.show {opacity: 1;transform: translateX(0);}.modal {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.8);display: flex;justify-content: center;align-items: center;z-index: 2000;opacity: 0;visibility: hidden;transition: all 0.3s ease;}.modal.show {opacity: 1;visibility: visible;}.modal-content {background: var(--card-bg);border-radius: 10px;padding: 30px;max-width: 600px;width: 90%;max-height: 80vh;overflow-y: auto;position: relative;transform: scale(0.8);transition: all 0.3s ease;border: 1px solid var(--border-color);box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);}.modal.show .modal-content {transform: scale(1);}.modal-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;padding-bottom: 15px;border-bottom: 1px solid var(--border-color);}.modal-title {font-family: 'Orbitron', sans-serif;font-size: 1.8rem;color: var(--primary-color);margin: 0;}.close-btn {background: none;border: none;color: var(--text-color);font-size: 1.5rem;cursor: pointer;padding: 0;width: 30px;height: 30px;display: flex;align-items: center;justify-content: center;border-radius: 50%;transition: all 0.3s ease;}.close-btn:hover {background: rgba(255, 255, 255, 0.1);transform: rotate(90deg);}.person-list {margin-bottom: 20px;max-height: 300px;overflow-y: auto;}.person-item {display: flex;justify-content: space-between;align-items: center;padding: 12px 15px;margin-bottom: 8px;background: rgba(255, 255, 255, 0.05);border-radius: 5px;transition: all 0.3s ease;border-left: 3px solid transparent;}.person-item:hover {background: rgba(255, 255, 255, 0.1);border-left-color: var(--primary-color);}.person-name {flex: 1;}.delete-person {background: none;border: none;color: #ff5252;cursor: pointer;padding: 5px 10px;border-radius: 3px;transition: all 0.3s ease;font-size: 0.9rem;}.delete-person:hover {background: rgba(255, 82, 82, 0.2);}.add-person-form {display: flex;gap: 10px;margin-top: 20px;}.person-input {flex: 1;padding: 12px 15px;background: rgba(255, 255, 255, 0.05);border: 1px solid var(--border-color);border-radius: 5px;color: var(--text-color);font-size: 1rem;transition: all 0.3s ease;}.person-input:focus {outline: none;border-color: var(--primary-color);box-shadow: 0 0 10px rgba(0, 229, 255, 0.2);background: rgba(255, 255, 255, 0.1);}.person-input::placeholder {color: rgba(255, 255, 255, 0.5);}.add-btn {padding: 12px 20px;background: linear-gradient(135deg, #4caf50, #2e7d32);box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);}.add-btn:hover {box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);}.save-btn {width: 100%;margin-top: 20px;padding: 15px;background: linear-gradient(135deg, var(--secondary-color), #ffab00);color: var(--bg-color);font-weight: 700;}.save-btn:hover {box-shadow: 0 6px 20px rgba(255, 234, 0, 0.4);}.count-display {position: absolute;top: 20px;left: 20px;background: var(--card-bg);padding: 10px 20px;border-radius: 20px;font-size: 0.9rem;font-weight: 600;border: 1px solid var(--border-color);display: flex;align-items: center;gap: 5px;}.count-number {color: var(--secondary-color);font-weight: 700;}.history {width: 100%;background: var(--card-bg);border-radius: 10px;padding: 20px;margin-top: 20px;border: 1px solid var(--border-color);}.history h3 {font-family: 'Orbitron', sans-serif;color: var(--primary-color);margin-bottom: 15px;font-size: 1.2rem;}.history-list {display: flex;flex-wrap: wrap;gap: 10px;}.history-item {background: rgba(255, 234, 0, 0.1);padding: 8px 15px;border-radius: 20px;font-size: 0.9rem;border: 1px solid rgba(255, 234, 0, 0.3);display: flex;align-items: center;gap: 5px;}.history-time {font-size: 0.8rem;color: rgba(255, 255, 255, 0.5);}/* 响应式设计 */@media (max-width: 768px) {h1 {font-size: 2rem;}h1::before, h1::after {display: none;}.person-display {font-size: 3rem;}.caller-box {padding: 20px;}.display-area {height: 200px;}.controls {flex-direction: column;align-items: center;}.controls button {width: 100%;max-width: 300px;}.count-display {position: static;margin-bottom: 20px;}}/* 滚动条样式 */::-webkit-scrollbar {width: 8px;}::-webkit-scrollbar-track {background: rgba(255, 255, 255, 0.05);border-radius: 4px;}::-webkit-scrollbar-thumb {background: rgba(0, 229, 255, 0.3);border-radius: 4px;}::-webkit-scrollbar-thumb:hover {background: rgba(0, 229, 255, 0.5);}</style>
</head>
<body><div class="container"><header><h1>随机点名系统</h1><p class="subtitle">科技感十足的随机抽取工具</p></header><div class="count-display"><span>总人数:</span><span class="count-number" id="person-count">{{ persons|length }}</span></div><div class="main-content"><div class="caller-box"><div class="display-area"><div class="person-display" id="person-display">点击开始</div></div><div class="controls"><button id="start-btn">开始点名</button><button id="stop-btn" disabled>停止点名</button><button id="settings-btn" class="secondary-btn">设置人员</button></div></div><div class="history" id="history"><h3>历史记录</h3><div class="history-list" id="history-list"><!-- 历史记录将在这里动态添加 --></div></div></div></div><!-- 设置人员弹窗 --><div class="modal" id="settings-modal"><div class="modal-content"><div class="modal-header"><h2 class="modal-title">设置人员名单</h2><button class="close-btn" id="close-modal">×</button></div><div class="person-list" id="person-list">{% for person in persons %}<div class="person-item"><span class="person-name">{{ person }}</span><button class="delete-person" data-name="{{ person }}">删除</button></div>{% endfor %}</div><div class="add-person-form"><input type="text" class="person-input" id="new-person" placeholder="输入姓名..."><button class="add-btn" id="add-person-btn">添加</button></div><button class="save-btn" id="save-persons-btn">保存设置</button></div></div><!-- 状态信息弹窗 --><div class="status-info" id="status-info"><span id="status-message">操作成功</span></div><script>// 全局变量let isRolling = false;let rollingInterval = null;let persons = [];let historyList = [];// DOM元素const personDisplay = document.getElementById('person-display');const startBtn = document.getElementById('start-btn');const stopBtn = document.getElementById('stop-btn');const settingsBtn = document.getElementById('settings-btn');const settingsModal = document.getElementById('settings-modal');const closeModal = document.getElementById('close-modal');const personList = document.getElementById('person-list');const newPersonInput = document.getElementById('new-person');const addPersonBtn = document.getElementById('add-person-btn');const savePersonsBtn = document.getElementById('save-persons-btn');const statusInfo = document.getElementById('status-info');const statusMessage = document.getElementById('status-message');const personCount = document.getElementById('person-count');const historyListEl = document.getElementById('history-list');// 初始化async function init() {// 从服务器获取人员名单const response = await fetch('/get_persons');const data = await response.json();persons = data.persons || [];// 初始化历史记录loadHistory();}// 加载历史记录function loadHistory() {const savedHistory = localStorage.getItem('randomCallerHistory');if (savedHistory) {historyList = JSON.parse(savedHistory);updateHistoryDisplay();}}// 保存历史记录function saveHistory() {localStorage.setItem('randomCallerHistory', JSON.stringify(historyList));}// 更新历史记录显示function updateHistoryDisplay() {historyListEl.innerHTML = '';// 只显示最近10条记录const recentHistory = historyList.slice(-10).reverse();recentHistory.forEach(item => {const historyItem = document.createElement('div');historyItem.className = 'history-item';historyItem.innerHTML = `<span>${item.person}</span><span class="history-time">${item.time}</span>`;historyListEl.appendChild(historyItem);});}// 添加历史记录function addHistoryItem(person) {const now = new Date();const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;historyList.push({person: person,time: timeString});saveHistory();updateHistoryDisplay();}// 显示状态信息function showStatus(message, isError = false) {statusMessage.textContent = message;statusInfo.classList.add('show');if (isError) {statusInfo.style.borderLeftColor = '#ff5252';} else {statusInfo.style.borderLeftColor = 'var(--primary-color)';}setTimeout(() => {statusInfo.classList.remove('show');}, 3000);}// 开始滚动function startRolling() {if (persons.length === 0) {showStatus('请先添加人员名单', true);return;}isRolling = true;startBtn.disabled = true;stopBtn.disabled = false;personDisplay.classList.add('rolling');// 播放滚动音效playRollingSound();// 设置滚动间隔rollingInterval = setInterval(() => {const randomIndex = Math.floor(Math.random() * persons.length);personDisplay.textContent = persons[randomIndex];}, 100);}// 停止滚动async function stopRolling() {if (!isRolling) return;// 清除滚动间隔clearInterval(rollingInterval);// 最后的动画效果let speed = 200;const finalRolling = setInterval(() => {const randomIndex = Math.floor(Math.random() * persons.length);personDisplay.textContent = persons[randomIndex];speed += 100;if (speed > 1000) {clearInterval(finalRolling);// 获取最终结果fetch('/random_call').then(response => response.json()).then(data => {if (data.success) {personDisplay.textContent = data.person;// 添加到历史记录addHistoryItem(data.person);// 播放结束音效playEndSound();} else {showStatus(data.message, true);}});// 恢复按钮状态isRolling = false;startBtn.disabled = false;stopBtn.disabled = true;personDisplay.classList.remove('rolling');}}, speed);}// 打开设置弹窗function openSettings() {settingsModal.classList.add('show');updatePersonList();}// 关闭设置弹窗function closeSettings() {settingsModal.classList.remove('show');}// 更新人员列表显示function updatePersonList() {personList.innerHTML = '';persons.forEach(person => {const personItem = document.createElement('div');personItem.className = 'person-item';personItem.innerHTML = `<span class="person-name">${person}</span><button class="delete-person" data-name="${person}">删除</button>`;personList.appendChild(personItem);});// 更新人数显示personCount.textContent = persons.length;// 为删除按钮添加事件监听document.querySelectorAll('.delete-person').forEach(btn => {btn.addEventListener('click', function() {const personToDelete = this.getAttribute('data-name');deletePerson(personToDelete);});});}// 添加人员function addPerson() {const newPerson = newPersonInput.value.trim();if (!newPerson) {showStatus('请输入人员姓名', true);return;}if (persons.includes(newPerson)) {showStatus('该人员已存在', true);return;}// 发送请求到服务器添加人员fetch('/add_person', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ person: newPerson })}).then(response => response.json()).then(data => {if (data.success) {persons = data.persons;updatePersonList();newPersonInput.value = '';showStatus('添加成功');} else {showStatus(data.message, true);}});}// 删除人员function deletePerson(person) {// 发送请求到服务器删除人员fetch('/remove_person', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ person: person })}).then(response => response.json()).then(data => {if (data.success) {persons = data.persons;updatePersonList();showStatus('删除成功');} else {showStatus(data.message, true);}});}// 保存人员名单function savePersons() {fetch('/save_persons', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ persons: persons })}).then(response => response.json()).then(data => {if (data.success) {showStatus('保存成功');closeSettings();} else {showStatus(data.message, true);}});}// 播放滚动音效function playRollingSound() {// 这里可以添加音效,但为了不自动播放,我们注释掉// const audio = new Audio('rolling-sound.mp3');// audio.play().catch(e => console.log('无法播放音效:', e));}// 播放结束音效function playEndSound() {// 这里可以添加音效,但为了不自动播放,我们注释掉// const audio = new Audio('end-sound.mp3');// audio.play().catch(e => console.log('无法播放音效:', e));}// 添加事件监听function addEventListeners() {startBtn.addEventListener('click', startRolling);stopBtn.addEventListener('click', stopRolling);settingsBtn.addEventListener('click', openSettings);closeModal.addEventListener('click', closeSettings);addPersonBtn.addEventListener('click', addPerson);savePersonsBtn.addEventListener('click', savePersons);// 按Enter键添加人员newPersonInput.addEventListener('keypress', function(e) {if (e.key === 'Enter') {addPerson();}});// 点击模态框背景关闭settingsModal.addEventListener('click', function(e) {if (e.target === settingsModal) {closeSettings();}});// ESC键关闭模态框document.addEventListener('keydown', function(e) {if (e.key === 'Escape' && settingsModal.classList.contains('show')) {closeSettings();}});}// 初始化应用async function initApp() {await init();addEventListeners();}// 启动应用initApp();</script>
</body>
</html>
最后的话
这个随机点名系统不仅是一个实用工具,更是一个学习编程的好例子。希望它能帮助你在课堂或活动中增加互动性,同时也能激发你对编程的兴趣。
如果你有任何问题或改进建议,欢迎在评论区留言讨论!