【亲测有效】【源码】抽奖程序4.0 抽奖过程随机展示动画 一次抽奖多人正确展示结果
"""
★★★★★ 增强版抽奖系统 ★★★★★
【功能清单】
✅ 核心功能:
- 批量导入TXT/CSV文件(支持多编码)
- 动态抽奖动画(2秒随机闪烁+聚焦效果)
- 多人中奖记录合并显示(逗号分隔)
- 中奖权重设置(CSV第二列)
- 历史记录导出(单条记录包含所有中奖者)
✅ 交互增强:
- 深色/浅色双主题切换
- MP3音效反馈(需ding.mp3文件)
- 树形表格展示历史记录
- 实时人数统计显示
✅ 高级设置:
- 排除已中奖者模式
- 中奖人数调节(1-10人)
- 时间格式自定义(3种预设)
- 音效开关控制
✅ 异常处理:
- 智能编码回退机制
- 文件错误精确定位
- 操作防呆设计
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import random
import datetime
import csv
import threading
from playsound import playsound # pip install playsound
class LotteryApp:
def __init__(self, root):
self.root = root
self.root.title("高级抽奖系统") # 设置窗口标题
self.root.geometry("800x600") # 初始化窗口尺寸
# === 数据存储 ===
self.participants = [] # 参与者名单(列表)
self.history = [] # 历史记录(元组列表)
self.weights = [] # 权重列表(浮点数)
self.current_candidates = [] # 动画候选名单(临时数据)
self.is_animating = False # 动画状态标记(布尔值)
# === 界面变量 ===
self.theme_mode = tk.StringVar(value="light") # 主题模式控制变量
self.exclude_winners = tk.BooleanVar() # 排除中奖者复选框状态
self.num_winners = tk.IntVar(value=1) # 中奖人数设置值
self.time_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S") # 时间格式
self.audio_enabled = tk.BooleanVar(value=True) # 音效开关状态
# === 界面构建 ===
self.create_widgets() # 创建所有界面组件
self.apply_theme("light") # 应用默认主题
def create_widgets(self):
"""构建GUI界面组件"""
# ---- 控制面板 ----
control_frame = ttk.Frame(self.root)
control_frame.pack(pady=10, fill=tk.X) # 顶部控制栏
# 批量导入按钮
ttk.Button(control_frame, text="批量导入",
command=self.batch_import).pack(side=tk.LEFT, padx=5)
# 导出历史按钮
ttk.Button(control_frame, text="导出历史",
command=self.export_history).pack(side=tk.LEFT, padx=5)
# 主题切换复选框
ttk.Checkbutton(control_frame, text="深色模式",
variable=self.theme_mode,
command=self.toggle_theme).pack(side=tk.RIGHT, padx=5)
# 音效开关
ttk.Checkbutton(control_frame, text="启用音效",
variable=self.audio_enabled).pack(side=tk.RIGHT, padx=5)
# ---- 抽奖设置面板 ----
settings_frame = ttk.LabelFrame(self.root, text="抽奖设置")
settings_frame.pack(pady=10, fill=tk.X) # 中部设置区域
# 排除中奖者选项
ttk.Checkbutton(settings_frame, text="排除已中奖者",
variable=self.exclude_winners).grid(row=0, column=0, padx=5)
# 中奖人数设置
ttk.Label(settings_frame, text="中奖人数:").grid(row=0, column=1, padx=5)
ttk.Spinbox(settings_frame, from_=1, to=10,
textvariable=self.num_winners, width=5).grid(row=0, column=2, padx=5)
# 时间格式选择
ttk.Label(settings_frame, text="时间格式:").grid(row=0, column=3, padx=5)
ttk.Combobox(settings_frame, textvariable=self.time_format,
values=["%Y-%m-%d", "%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
).grid(row=0, column=4, padx=5)
# ---- 抽奖主按钮 ----
self.lottery_btn = ttk.Button(self.root, text="开始抽奖",
command=self.start_lottery_process)
self.lottery_btn.pack(pady=20) # 中央抽奖按钮
# ---- 结果展示 ----
self.result_label = ttk.Label(self.root, text="等待抽奖...",
font=("Arial", 16), foreground="red")
self.result_label.pack() # 动态结果展示区域
# ---- 历史记录表格 ----
history_frame = ttk.LabelFrame(self.root, text="历史记录")
history_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10) # 底部扩展区域
# 使用Treeview组件显示历史
self.history_tree = ttk.Treeview(history_frame, columns=("time", "winner"), show="headings")
self.history_tree.heading("time", text="时间") # 时间列标题
self.history_tree.heading("winner", text="中奖者") # 中奖者列标题
self.history_tree.pack(expand=True, fill=tk.BOTH) # 自适应填充
# ========== 核心功能方法 ==========
def batch_import(self):
"""批量导入名单文件(支持TXT/CSV)"""
file_paths = filedialog.askopenfilenames(
filetypes=[("Text Files", "*.txt"), ("CSV Files", "*.csv")])
if not file_paths:
return # 用户取消选择
self.show_loading(True) # 显示加载提示
total_import = 0
for path in file_paths:
try:
encoding = self.detect_encoding(path) # 自动检测编码
with open(path, 'r', encoding=encoding) as f:
if path.endswith(".csv"):
# 处理带权重的CSV文件
reader = csv.reader(f)
for row in reader:
if len(row) >= 2: # 至少包含名字和权重两列
self.participants.append(row[0].strip())
self.weights.append(float(row[1]))
else:
# 处理普通TXT文件
self.participants.extend(
line.strip() for line in f if line.strip())
total_import += 1
except Exception as e:
messagebox.showerror("错误", f"文件 {path.split('/')[-1]} 导入失败:{str(e)}")
self.show_loading(False)
messagebox.showinfo("导入完成",
f"成功导入 {total_import}/{len(file_paths)} 个文件\n当前人数:{len(self.participants)}")
def start_lottery_process(self):
"""启动抽奖流程(入口方法)"""
if not self.participants:
messagebox.showwarning("警告", "请先导入抽奖名单!")
return
self.lottery_btn.config(state=tk.DISABLED) # 禁用按钮防止重复点击
self.result_label.config(text="抽奖进行中...") # 状态提示
threading.Thread(target=self.run_animation_sequence).start() # 启动动画线程
def run_animation_sequence(self):
"""运行动画效果(包含两个阶段)"""
self.is_animating = True
self.current_candidates = self.participants.copy() # 初始化候选名单
# === 第一阶段:快速闪烁(1.5秒) ===
for _ in range(30): # 30次 × 50ms = 1500ms
random.shuffle(self.current_candidates) # 随机打乱顺序
display_text = " ✨ ".join(self.current_candidates[:5]) # 显示前5个带特效符号
self.update_result_label(display_text) # 更新显示
threading.Event().wait(0.05) # 50ms间隔
# === 第二阶段:减速聚焦(0.5秒) ===
candidates = self.current_candidates.copy()
for i in range(10): # 10次,间隔逐渐增加
keep_num = max(1, len(candidates) - 2) # 每次减少2个候选
candidates = random.sample(candidates, keep_num) # 随机保留部分
display_text = " → ".join(candidates) # 用箭头连接
self.update_result_label(display_text)
threading.Event().wait(0.05 + i*0.02) # 间隔从50ms增加到250ms
# === 最终抽奖 ===
self.is_animating = False
self.draw_lottery() # 执行实际抽奖
self.lottery_btn.config(state=tk.NORMAL) # 恢复按钮状态
def draw_lottery(self):
"""执行核心抽奖逻辑"""
try: # 尝试播放音效
if self.audio_enabled.get():
threading.Thread(target=playsound, args=('ding.mp3',)).start()
except Exception as e:
print(f"音效播放失败: {e}")
# 确定实际中奖人数(不超过剩余人数)
num = min(self.num_winners.get(), len(self.participants))
winners = random.choices(
population=self.participants,
weights=self.weights if self.weights else None, # 应用权重
k=num
)
# 排除已中奖者模式处理
if self.exclude_winners.get():
self.participants = [p for p in self.participants if p not in winners]
# === 记录抽奖结果 ===
time_str = datetime.datetime.now().strftime(self.time_format.get())
if winners:
# 合并多人中奖记录(关键修改点)
winner_str = ", ".join(winners) # 用逗号分隔多个中奖者
record = (time_str, winner_str) # 单条记录包含所有中奖者
self.history.append(record) # 添加到历史数据
self.history_tree.insert("", 0, values=record) # 插入到表格顶部
# 显示抽奖结果
result_text = " 🎉 ".join(winners) # 用彩带符号分隔
self.result_label.config(text=f"中奖者:{result_text}")
# ========== 工具方法 ==========
def detect_encoding(self, file_path):
"""自动检测文件编码"""
encodings = ['utf-8', 'gbk', 'gb18030', 'big5'] # 常见中文编码
for enc in encodings:
try:
with open(file_path, 'r', encoding=enc) as f:
f.read(1024) # 尝试读取前1KB内容
return enc # 成功则返回编码
except:
continue
return 'latin-1' # 最终回退编码
def export_history(self):
"""导出历史记录到文件"""
path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV Files", "*.csv"), ("Text Files", "*.txt")]
)
if not path:
return # 用户取消保存
try:
with open(path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(["时间", "中奖者"]) # 写入标题行
# 倒序写入保持最新记录在前(与界面显示一致)
writer.writerows(reversed(self.history))
messagebox.showinfo("成功", "历史记录已导出!")
except Exception as e:
messagebox.showerror("错误", f"导出失败:{str(e)}")
def update_result_label(self, text):
"""线程安全更新结果标签"""
self.root.after(0, lambda: self.result_label.config(text=text))
def show_loading(self, show):
"""显示/隐藏加载提示"""
if show:
self.loading = ttk.Label(self.root, text="正在加载...")
self.loading.pack(pady=10)
else:
if hasattr(self, 'loading'):
self.loading.destroy()
def toggle_theme(self):
"""切换主题模式"""
theme = "dark" if self.theme_mode.get() == "dark" else "light"
self.apply_theme(theme)
def apply_theme(self, theme):
"""应用主题样式到界面"""
colors = {
"light": {"bg": "#FFFFFF", "fg": "#333333"}, # 浅色主题
"dark": {"bg": "#2D2D2D", "fg": "#E6E6E6"} # 深色主题
}
style = ttk.Style()
style.theme_use("clam") # 使用现代主题
style.configure(".",
background=colors[theme]["bg"],
foreground=colors[theme]["fg"]) # 全局样式
self.root.config(bg=colors[theme]["bg"]) # 主窗口背景色
# ========== 程序入口 ==========
if __name__ == "__main__":
root = tk.Tk() # 创建主窗口
app = LotteryApp(root) # 实例化应用
root.mainloop() # 启动事件循环
关键优化说明:
- 多人记录合并
# 原代码(每人单独记录)
for winner in winners:
record = (time_str, winner)
self.history.append(record)
# 新代码(合并多人记录)
winner_str = ", ".join(winners) # 用逗号连接多个名字
record = (time_str, winner_str) # 单条记录包含所有中奖者
self.history.append(record)
- 导出格式优化
时间,中奖者
"2023-07-21 10:00:00","张三, 李四, 王五"
"2023-07-21 10:05:00","赵六"
- 界面显示改进
# 历史记录表格显示效果
+---------------------+-------------------+
| 时间 | 中奖者 |
+---------------------+-------------------+
| 2023-07-21 10:00:00 | 张三, 李四, 王五 |
| 2023-07-21 09:59:00 | 赵六 |
+---------------------+-------------------+
运行准备:
- 安装依赖:
pip install playsound
- 准备音效文件:在程序目录添加
ding.mp3
- 测试文件示例(带权重的CSV):
张三,1.5 李四,2.0 王五,1.0
操作指南:
- 点击
批量导入
选择多个文件 - 设置中奖人数和其他选项
- 点击
开始抽奖
观看动态动画 - 使用
导出历史
保存CSV文件 - 通过
深色模式
切换界面主题
代码学习,前言技术分享,深度分析编程技术,普及科普编程技术,天天都要敲代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)