Python基于Tkinter设计公司年会抽奖系统(第19节)


公司年会是企业文化和团队精神的重要体现之一,在年会上,一般会安排一些创意的节目和抽奖环节,而抽奖环节则是最令人期待的环节之一。年会抽奖是年会活动中用于活跃气氛和奖励员工的重要环节,通常通过大屏幕展示抽奖过程,能给员工带来无穷的喜悦感。 ‌传统的抽奖箱抽奖既费时又费力,我们完全可以使用Python代码来实现一个专属的抽奖程序。

1、公司年会抽奖系统介绍

在本教程中,我们将通过Python的Tkinter库构建一个简单的抽奖系统,该抽奖系统主要包括两个核心窗口页面,分别是抽奖的主窗口和奖项设置窗口,在Tkinter应用程序的主窗口里,可以通过“设置奖项”按钮打开奖项设置窗口,在弹出的输入框中自定义输入“奖项名称”和“获奖人数”,比如,特等奖设定1名,一等奖设定2名,以此类推。

为了使抽奖系统使用更方便,我们可以从Excel文件中导入员工名单。假设我们有一个名为“name.xlsx”的Excel文件,每行包含一个员工姓名。如下图所示:

Python基于Tkinter设计公司年会抽奖系统

导入Excel文件后,从Excel文件中读入第一列所有的员工姓名,再将员工姓名添加到列表中,用random模块的choice函数来随机选择列表中的一个姓名,最后中奖者姓名将从列表中删除,以免再次中奖。

导入员工姓名和根据要求设置奖项后,就可以点击“开始抽奖”按钮,系统会显示动画抽奖效果,并根据抽奖前后人数与奖项变化逻辑,实时更新显示内容。

2、公司年会抽奖系统界面演示

Python基于Tkinter设计公司年会抽奖系统

Python基于Tkinter设计公司年会抽奖系统

Python基于Tkinter设计公司年会抽奖系统

3、代码实现

(1)安装pandas库

首先,确保你已经安装了pandas库。如果还没有安装,可以通过pip安装:

pip install pandas

(2)导入必要的库

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import random, webbrowser

这里导入了Tkinter库用于创建GUI应用图形界面,Tkinter的filedialog模块用于处理文件选择、保存等操作,Tkinter的messagebox用于消息提示。Pandas库的pandas.read_excel()函数可以从Excel文件中读取数据。random库是用于生成和运用随机数的标准库。Python的webbrowser库能够调用系统的默认浏览器或指定浏览器,打开指定的URL或本地的HTML文件。

(3)公司年会抽奖系统的完整代码如下所示:

动手练一练:

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import random, webbrowser

# 定义PrizeDraw类,用于封装抽奖系统的功能
class PrizeDraw:
    # 构造函数初始化对象
    def __init__(self, root):
        # 定义root参数,代表Tkinter的根窗口
        self.root = root
        self.root.title("公司年会抽奖系统")
        # 设置窗口居中
        window_width = 850
        window_height = 660
        screen_width = self.root .winfo_screenwidth()
        screen_height = self.root .winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        self.root .geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        self.root .resizable(width=False, height=False)
        # 定义空列表,用于存放当前可抽奖员工列表
        self.members = []
        # 定义布尔值,用于启动或暂停抽奖动画
        self.rolling_status = False
        # 定义空列表,用于存放奖项列表
        self.awards = [] 
        # 初始化奖项索引,“-1”表示当前未设置奖项
        self.award_set_state = -1
        # 创建菜单栏,包括“文件”和“设置”菜单
        self.set_menu()
        # 创建抽奖系统主界面
        self.set_widgets()
        # 动态更新抽奖按钮状态
        self.show_button_update()

    # 定义菜单界面,包括“文件”和“设置”菜单
    def set_menu(self):
        menu_part = tk.Menu(self.root)
        add_menu = tk.Menu(menu_part, tearoff=0)
        add_menu.add_command(label="导入员工名单", command=self.import_members)
        add_menu.add_command(label="导出中奖结果", command=self.export_winners)
        add_menu.add_command(label="关于程序", command=self.program_about)
        add_menu.add_command(label="程序讲解", command=self.help_window)
        add_menu.add_separator()  
        add_menu.add_command(label="退出", command=self.root.quit)
        menu_part.add_cascade(label="文件", menu=add_menu)
        settings_menu = tk.Menu(menu_part, tearoff=0)
        settings_menu.add_command(label="管理奖项", command=self.manage_awards)
        settings_menu.add_command(label="重置系统", command=self.reset_data)
        menu_part.add_cascade(label="设置", menu=settings_menu)
        self.root.config(menu=menu_part)

    # 程序讲解页面
    def help_window(self):
        webbrowser.open("https://www.pyhint.com/article/152.html")

    # 定义“关于程序”页面
    def program_about(self):
        about = tk.Tk()
        about.title('关于程序')

        # 设置窗口居中
        window_width = 350
        window_height = 350
        screen_width = about.winfo_screenwidth()
        screen_height = about.winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        about.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        about.resizable(width=False, height=False)

        about_frame = tk.Frame(about, width=320, height=320)
        about_frame.pack()
        tk.Label(about_frame, text='公司年会抽奖系统', font=("宋体", 12)).place(x=90, y=20)
        tk.Label(about_frame, text='使用编程语言:Python', font=("宋体", 12)).place(x=50, y=90)
        tk.Label(about_frame, text='导入名单方式:从Excel文件导入', font=("宋体", 12)).place(x=50, y=150)
        tk.Label(about_frame, text='保存中奖名单方式:导出到Excel文件', font=("宋体", 12)).place(x=50, y=210)
        tk.Label(about_frame, text='创作者:www.pyhint.com', font=("宋体", 12)).place(x=50, y=270)
        about.mainloop()

    # 定义抽奖系统主界面
    def set_widgets(self):
        base_frame = tk.Frame(self.root)
        base_frame.place(relx=0.5, rely=0.5, anchor="center")
        # 创建一个StringVar变量并设置默认值
        title_text = tk.StringVar()
        # 设置变量的初始值
        title_text.set("XX公司年会抽奖活动(可编辑)")
        # 定义标题输入框,打开程序时可输入自己的公司名称
        self.title_label = tk.Entry(base_frame, text=title_text, font=('宋体', 36, 'bold'), bg="white", width=30, justify='center')
        self.title_label.pack(pady=20)

        # 创建一个框架,用于导入抽奖名单
        load_frame = tk.Frame(base_frame)
        load_frame.pack(pady=10)
        # 左边创建一个标签显示“已加载人数”
        self.member_count = tk.Label(load_frame, text="已加载员工:0人", font=("宋体", 12))
        self.member_count.pack(padx=10, pady=10, side=tk.LEFT)
        # 右边创建“导入员工名单”按钮并绑定到import_members函数
        load_button = tk.Button(load_frame, text="导入员工名单", command=self.import_members, width=15)
        load_button.pack(padx=10, pady=10, side=tk.RIGHT)

        # 创建一个标签,用于显示抽奖动画
        self.name_label = tk.Label(base_frame, text="抽奖准备就绪……", font=('宋体', 42, 'bold'), width=20)
        self.name_label.pack(pady=20)

        # 创建一个框架,用于显示中奖结果
        show_frame = tk.Frame(base_frame)
        show_frame.pack(pady=10)
        self.winner_form = ttk.Treeview(show_frame, columns=("name", "award"), show="headings", height=10)
        self.winner_form.heading("name", text="姓名")
        self.winner_form.heading("award", text="奖项")
        self.winner_form.grid(row=1, columnspan=1)
        # 创建滚动条
        scrollbar = ttk.Scrollbar(show_frame, orient="vertical", command=self.winner_form.yview)
        scrollbar.grid(row=1, column=1, sticky="ns")

        # 将滚动条控件与Treeview控件关联
        self.winner_form.config(yscrollcommand=scrollbar.set)

        # 创建一个框架,用于存放抽奖过程按钮
        button_frame = tk.Frame(base_frame)
        button_frame.pack(pady=10)
        self.begin_button = tk.Button(button_frame, text="开始抽奖", command=self.start_lottery, width=15)
        self.begin_button.pack(side="left", padx=10)
        self.stop_button = tk.Button(button_frame, text="停止抽奖", command=self.stop_lottery, width=15, state="disabled")
        self.stop_button.pack(side="left", padx=10)
        self.settings_Button = tk.Button(button_frame, text="设置奖项", command=self.manage_awards, width=15)
        self.settings_Button.pack(side="left", padx=10)
        self.refresh_button = tk.Button(button_frame, text='复位', command=self.reset_data, width=10)
        self.refresh_button.pack(side="left", padx=10)

        # 创建一个框架,用于显示抽奖过程的实时奖项名称和剩余名额
        self.award_frame = tk.Frame(base_frame)
        self.award_frame.pack(pady=10)
        self.show_now_award = tk.Label(self.award_frame, text="当前奖项:未设置", font=("宋体", 12))
        self.show_now_award.pack()
        self.show_remaining = tk.Label(self.award_frame, text="剩余名额:0", font=("宋体", 12))
        self.show_remaining.pack()

    # 定义复位函数,清空所有数据
    def reset_data(self):
        # 清空员工名单
        self.members.clear()
        self.root.update_idletasks()
        self.awards = []
        self.name_label.config(text="抽奖准备就绪……")
        # 删除所有条目
        self.winner_form.delete(*self.winner_form.get_children())
        self.show_now_award_update()
        self.member_count.config(text='已加载员工:0人')
        self.show_button_update()

    # 更新抽奖按钮状态,如果完成抽奖操作,“抽奖”按钮将立刻变成禁用状态
    def show_button_update(self):
        if self.award_set_state >= len(self.awards):
            self.begin_button.config(state='disabled')
        else:
            self.begin_button.config(state='normal')

    # 实时更新奖项显示
    def show_now_award_update(self):
        if not self.awards:
            self.award_set_state = -1
            self.show_now_award.config(text="当前奖项:未设置")
            self.show_remaining.config(text="剩余名额:0")
            return
        if self.award_set_state == -1:
            self.award_set_state = 0
        if 0 <= self.award_set_state < len(self.awards):
            now_award = self.awards[self.award_set_state]
            remaining = now_award["quota"] - len(now_award["winners"])
            self.show_now_award.config(text=f"当前奖项:{now_award['name']}")
            self.show_remaining.config(
                text=f"剩余名额:{remaining} ({len(now_award['winners'])}/{now_award['quota']})")
        else:
            self.show_now_award.config(text="所有奖项已抽取完毕")
            self.show_remaining.config(text="剩余名额:0")

    # 从Excel表格中导入抽奖名单
    def import_members(self):
        save_path = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx")])
        if save_path:
            try:
                df = pd.read_excel(save_path, usecols='A', header=None)
                column_list = df[df.columns[0]].tolist()
                self.members = column_list.copy()
                self.member_count.config(text=f"已加载员工:{len(self.members)}人")
                messagebox.showinfo("成功", "员工名单导入成功!")
            except Exception as e:
                messagebox.showerror("错误", f"文件读取失败:{str(e)}")

    # 定义奖项管理界面
    def manage_awards(self):
        set_award = tk.Toplevel(self.root)
        set_award.title("奖项管理")

        # 设置窗口居中
        window_width = 400
        window_height = 380
        screen_width = set_award.winfo_screenwidth()
        screen_height = set_award.winfo_screenheight()
        x = (screen_width - window_width) / 2
        y = (screen_height - window_height) / 2
        set_award.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
        set_award.resizable(width=False, height=False)

        # 创建“奖项名称”和“获奖人数”输入框,可以自定义输入
        tk.Label(set_award, text="奖项名称:").grid(row=0, column=0, padx=10, pady=10, sticky="e")
        award_name = tk.Entry(set_award)
        award_name.grid(row=0, column=1, padx=10, pady=10, sticky="w")
        tk.Label(set_award, text="获奖人数:").grid(row=1, column=0, padx=10, pady=10, sticky="e")
        award_count = tk.Entry(set_award)
        award_count.grid(row=1, column=1, padx=10, pady=10, sticky="w")

        # 定义“添加奖项”函数
        def award_add():
            name = award_name.get().strip()
            count_get = award_count.get().strip()
            if not name:
                messagebox.showerror("错误", "奖项名称不能为空")
                return
            try:
                count = int(count_get)
                if count <= 0:
                    raise ValueError
            except ValueError:
                messagebox.showerror("错误", "请输入有效的正整数")
                return
            if any(award["name"] == name for award in self.awards):
                messagebox.showerror("错误", "该奖项名称已存在")
                return
            self.awards.append({
                "name": name,
                "quota": count,
                "winners": []
            })
            listbox_update()
            award_name.delete(0, tk.END)
            award_count.delete(0, tk.END)
            self.show_now_award_update()

        # 删除已设置的奖项
        def award_delete():
            selected = listbox.curselection()
            if not selected:
                messagebox.showwarning("警告", "请先选择奖项再删除")
                return
            index = selected[0]
            if len(self.awards[index]["winners"]) > 0:
                messagebox.showwarning("警告", "该奖项已有中奖者,不能删除")
                return
            del self.awards[index]
            listbox_update()
            self.show_now_award_update()

        # 定义退出函数
        def user_confirm():
            set_award.destroy()

        add_button = tk.Button(set_award, text="添加奖项并保存", command=award_add)
        add_button.grid(row=2, columnspan=2, padx=10, pady=10)

        listbox = tk.Listbox(set_award, width=50)
        listbox.grid(row=3, columnspan=2, padx=10, pady=5)

        del_button = tk.Button(set_award, text="删除选中奖项", command=award_delete)
        del_button.grid(row=4, column=0, padx=10, pady=5, sticky="e")

        # 创建“确认设置”按钮
        confirm_button = tk.Button(set_award,text='确认设置', command=user_confirm)
        confirm_button.grid(row=4, column=1, padx=10, pady=5)

        # 显示已设置的奖项
        def listbox_update():
            listbox.delete(0, tk.END)
            for award in self.awards:
                listbox.insert(tk.END, f"{award['name']} - {award['quota']}人")
        listbox_update()
        set_award.transient(self.root)
        set_award.grab_set()
        self.root.wait_window(set_award)

    # 检查奖项状态
    def check_awards_status(self):
        if self.award_set_state >= len(self.awards):
            return False
        now_award = self.awards[self.award_set_state]
        return len(now_award['winners']) < now_award['quota']

    # 定义函数,用于绑定“开始抽奖”按钮
    def start_lottery(self):
        if not self.members:
            messagebox.showwarning("警告", "请导入员工名单文件")
            return
        if not self.awards:
            messagebox.showwarning("警告", "请先设置奖项")
            return
        if self.award_set_state >= len(self.awards):
            messagebox.showinfo('提示', '所有奖项已抽取完毕,感谢参与!')
            self.begin_button.config(state='disabled')
            return
        while self.award_set_state < len(self.awards):
            now_award = self.awards[self.award_set_state]
            if len(now_award['winners']) < now_award['quota']:
                break
            self.award_set_state += 1
        if self.award_set_state >= len(self.awards):
            messagebox.showinfo('提示', '所有奖项已抽取完毕,感谢参与!')
            self.begin_button.config(state='disabled')
            return
        if self.award_set_state >= len(self.awards):
            self.award_set_state = 0
        now_award = self.awards[self.award_set_state]
        if len(now_award["winners"]) >= now_award["quota"]:
            self.award_set_state += 1
            if self.award_set_state >= len(self.awards):
                messagebox.showinfo("提示", "所有奖项已抽取完毕,感谢参与!")
                return
        self.rolling_status = True
        self.begin_button.config(state="disabled")
        self.stop_button.config(state="normal")
        self.roll()

    # 定义滚动效果函数
    def roll(self):
        if self.rolling_status and self.members:
            current_name = random.choice(self.members)
            self.name_label.config(text=current_name)
            self.root.after(50, self.roll)
        else:
            self.stop_lottery()

    # 定义函数,用于绑定“停止抽奖”按钮
    def stop_lottery(self):
        if not self.rolling_status:
            return
        self.rolling_status = False
        self.begin_button.config(state="normal")
        self.stop_button.config(state="disabled")
        if not self.members:
            messagebox.showwarning("警告", "员工名单已空")
            return
        now_winner = self.name_label.cget("text")
        if now_winner not in self.members:
            return
        try:
            now_award = self.awards[self.award_set_state]
            now_award["winners"].append(now_winner)
            self.winner_form.insert("", "end", values=(now_winner, now_award["name"]))
            self.members.remove(now_winner)
            self.member_count.config(text=f"已加载员工:{len(self.members)}人")
            self.show_now_award_update()
            if len(now_award["winners"]) >= now_award["quota"]:
                self.award_set_state += 1
                if self.award_set_state < len(self.awards):
                    messagebox.showinfo("提示", f"{now_award['name']}抽奖完成,即将开始下一个奖项")
                self.show_now_award_update()
        except IndexError:
            messagebox.showerror("错误", "无效的奖项索引")
        except ValueError:
            messagebox.showerror("错误", "找不到中奖员工")
        if self.award_set_state >= len(self.awards):
            self.begin_button.config(state='disabled')
            messagebox.showinfo('提示', '所有奖项已抽取完毕,感谢参与!')
            self.root.update_idletasks()
            return
        if not self.check_awards_status():
            self.begin_button.config(state='disabled')
        self.root.update_idletasks()

    # 导出抽奖结果到指定Excel表格
    def export_winners(self):
        if not any(len(award["winners"]) > 0 for award in self.awards):
            messagebox.showwarning("警告", "没有中奖数据可导出")
            return
        try:
            data = []
            for award in self.awards:
                for winner in award["winners"]:
                    data.append([winner, award["name"]])
            df = pd.DataFrame(data, columns=["姓名", "奖项"])
            save_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                filetypes=[("Excel文件", "*.xlsx")])
            if save_path:
                df.to_excel(save_path, index=False)
                messagebox.showinfo("成功", "导出成功!")
        except PermissionError:
            messagebox.showerror("错误", "文件被占用,请关闭文件后重试")
        except Exception as e:
            messagebox.showerror("错误", f"导出失败:{str(e)}")

# 当前模块直接被执行
if __name__ == "__main__":
    # 创建Tkinter窗口
    root = tk.Tk()
    # 实例化抽奖主页面
    app = PrizeDraw(root)
    # 开启主循环,让窗口处于显示状态
    root.mainloop()