import tkinter as tk from tkinter import ttk import yaml import colorama import threading import random import time import sys import csv import functools from PIL import Image Image.CUBIC = Image.BICUBIC #Image.CUBIC = Resampling.BICUBIC import ttkbootstrap as tb marking_buttons = list() version='v2.1' config = None current_member = dict() member_sum = 0 stdtime = 0.1 random_stop = 0 LICENSE = None member = None is_paused = 1 is_tab_shown = 0 default_tab_layout = None is_first = 1 db = None marking_colormark = dict() # 标记颜色序号 is_dark = None mk_fg = [] mk_bg = [] btnbg = None style = None btnfg = None ttkthemename = None about_text = f"""CLASSAUX - 幸运选手α 带轻量级数据库和随机功能的教学管理辅助程序 版本 {version} 版权所有 (C) 2025 Wang Zhiyu 本程序为共产版权的自由软件, 在自由软件联盟发布的GNU通用公共许可协议的约束下, 你可以对其进行再发布及修改。协议版本为第三版。 我们希望发布的这款程序有用, 但不保证, 甚至不保证它有经济价值和适合特定用途。 详情参见 GNU 通用公共许可协议 你理当已收到一份GNU通用公共许可协议的副本, 如果没有, 请查阅 -------------------------------------------------------------------------------- 使用的第三方资源与开放源代码库: Colorama - Cross-platform colored terminal text Tkinter - Python interface to Tcl/Tk Pillow - Python Imaging Library PyYaml - YAML parser and emitter for Python Sarasa Gothic (更纱黑体) -------------------------------------------------------------------------------- 开源软件是一项立足于协作精神的事业, 它的运作和产出不受任何单一个人或者机构的控制。 不管您来自何方,我们都欢迎您加入并做出贡献。 开放源代码/报告程序缺陷: https://github.com/david-ajax/classaux -------------------------------------------------------------------------------- """ current_member["pause"] = None current_member["show"] = "" def init_theme(): global ttkthemename global mk_bg global mk_fg global style global btnfg global btnbg global sbtnfg global sbtnbg if not is_dark: mk_bg = ["#FFFFFF","#009900","#ff6666","#0088ff"] mk_fg = ["#000000","#FFFFFF","#FFFFFF","#FFFFFF"] ttkthemename = "cosmo" btnbg = [('pressed', '#2097ff'), ('active', '#9bcbff')] btnfg = [('pressed', 'white'), ('active', '#36393b')] sbtnbg = 'white' sbtnfg = '#36393b' else: mk_fg = ["#FFFFFF","#00ff00","#ff6666","#9999ff"] mk_bg = ["#202020","#005500","#550000","#000055"] ttkthemename = "darkly" btnbg = [('pressed', '#242424'), ('active', '#242424')] btnfg = [('pressed', '#FFFFFF'), ('active', '#dddddd')] sbtnbg = "#202020" sbtnfg = 'white' def matrix_gen(format): ttmem = list() tmem = list() mem = list(config["member"]) random.shuffle(mem) print(list(enumerate(mem))) for i, ele in enumerate(mem): tmem.append(ele["name"]) if i % 8 == 0: ttmem.append(tmem) tmem = list() ttmem.reverse() omember = None def random_gen(): global config global current_member global random_stop global member member = config['member'] while not random_stop: #print("重置列表") random.shuffle(member) #print(member) for i in member: if random_stop: break #print(i) if i['weight'] == 0: pass else: for k in range(0, i['weight']): current_member["genuine"] = i['name'] time.sleep(stdtime) def interface(): global btnfg global btnbg global style global ttkthemename global sbtnbg global sbtnfg root = tb.Window(themename=ttkthemename) #root = tk.Tk() root.iconphoto(False, tk.PhotoImage(file='logo.png')) style = tb.Style() style.configure("Marking.TButton", background=sbtnbg, foreground=sbtnfg) style.map("Marking.TButton", background=btnbg, foreground=btnfg) style.configure("TabToggle.TButton", background=sbtnbg, foreground=sbtnfg) style.map("TabToggle.TButton", background=btnbg, foreground=btnfg) root.title(f"幸运选手α {version}") root.geometry("800x600") notebook = ttk.Notebook(root) notebook.pack(padx=10, pady=10) tab_electing = tk.Frame(notebook, width=800, height=600) tab_marking = tk.Frame(notebook, width=800, height=600) tab_about = tk.Frame(notebook, width=800, height=600) tab_bat = tk.Frame(notebook, width=800, height=600) tab_edit = tk.Frame(notebook, width=800, height=600) tab_filter = tk.Frame(notebook, width=800, height=600) tab_migration = tk.Frame(notebook, width=800, height=600) tab_cfg = tk.Frame(notebook, width=800, height=600) notebook.add(tab_electing, text='单次抽签') notebook.add(tab_marking, text='标记视图') notebook.add(tab_bat, text='批量视图') notebook.add(tab_edit, text='编辑数据库') notebook.add(tab_filter, text='配置筛选器') notebook.add(tab_cfg, text='设置') notebook.add(tab_migration, text='转换兼容配置') notebook.add(tab_about, text='关于') marking_colormode = tk.StringVar() bat_formationmode = tk.StringVar() bat_attrmode = tk.StringVar() chosen_attr = tk.StringVar() def update_data(): global is_paused if not is_paused: wdgt[tab_electing][0].config(text=config['member'][random.randint(0, member_sum - 1)]["name"]) else: wdgt[tab_electing][0].config(text=current_member["show"]) wdgt[tab_electing][0].after(int(1000 * stdtime * 0.75), update_data) def on_closing(): root.destroy() global random_stop random_stop = 1 def toggle(): # 仅用于单次抽取模式 global is_paused if not is_paused: current_member["show"] = current_member["genuine"] is_paused = 1 wdgt[tab_electing][1].config(text="开始") else: wdgt[tab_electing][1].config(text="暂停") is_paused = 0 def toggle_menu(): global is_tab_shown global default_tab_layout if not is_tab_shown: style.layout('TNotebook.Tab', default_tab_layout) is_tab_shown = 1 else: style.layout('TNotebook.Tab', []) is_tab_shown = 0 def toggle_dark(): global style global is_dark global ttkthemename global btnbg global btnfg global sbtnbg global sbtnfg global is_tab_shown is_dark = not is_dark init_theme() style.theme_use(ttkthemename) style.configure("Marking.TButton", background=sbtnbg, foreground=sbtnfg) style.map("Marking.TButton", background=btnbg, foreground=btnfg) style.configure("TabToggle.TButton", background=sbtnbg, foreground=sbtnfg) style.map("TabToggle.TButton", background=btnbg, foreground=btnfg) marking_clear() is_tab_shown = not is_tab_shown toggle_menu() def marking_clear(): global style global marking_buttons marking_colormark.clear() for i in marking_buttons: marking_change_color(i) wdgt = { tab_electing: [ ttk.Label(tab_electing, text="待抽取", font=("Sarasa UI SC", 70, 'bold')), ttk.Button(tab_electing, text="开始", command=toggle), ttk.Button(tab_electing, text="选项卡", command=toggle_menu,style="TabToggle.TButton"), ttk.Button(tab_electing, text="暗色调", command=toggle_dark,style="TabToggle.TButton"), ], tab_about: [ tk.Text(tab_about, wrap=tk.WORD,font=("Sarasa UI SC", 10, '')), #ttk.Label(tab_about, text=about_text) ], tab_edit: [tk.Text(tab_about, wrap=tk.WORD,font=("Sarasa UI Mono SC", 10, '')), ], tab_marking: [ ttk.Button(tab_marking, text="清空",command=marking_clear), ttk.Checkbutton(tab_marking, text="只读模式", onvalue=1, offvalue=0,cursor="arrow",state="normal"), ttk.Combobox(tab_marking, textvariable=marking_colormode, values=("双值模式", "三值模式", "五值模式")) ], tab_bat: [ tk.Text(tab_bat), ttk.Button(tab_bat, text="生成新随机", command=matrix_gen), ttk.Button(tab_bat, text="复制到剪贴板",style="TabToggle.TButton"), ttk.Button(tab_bat, text="导出至文件 (CSV/TXT)",style="TabToggle.TButton"), ttk.Combobox(tab_bat, textvariable=bat_formationmode, values=("选择格式...", "线性列表", "ASCII 表格", "CSV (逗号分隔)", "Markdown 表格")), ttk.Combobox(tab_bat, textvariable=bat_attrmode, values=("选择随机项...", "姓名", "性别", "居住")) ], tab_filter: [ ttk.Combobox(tab_filter,textvariable=chosen_attr, values=["选择筛选项...","示例1","示例2"]), ttk.Label(tab_filter, text="属性: 无"), ttk.Label(tab_filter, text="类型: 无"), ttk.Label(tab_filter, text="注意: 更改是实时的") ], tab_migration: [ ttk.Combobox(tab_filter,textvariable=chosen_attr, values=["选择兼容的配置文件格式...","示例1","示例2"]), ttk.Label(tab_filter, text="支持的文件格式: TXT 姓名列表(回车分隔), 标准 CSV 文件"), ], tab_cfg: [ ttk.Label(tab_cfg, text="外观",font=("Sarasa UI SC", 14, 'bold')), ttk.Label(tab_cfg, text="功能",font=("Sarasa UI SC", 14, 'bold')), ttk.Label(tab_cfg, text="调试",font=("Sarasa UI SC", 14, 'bold')), ttk.Combobox(tab_cfg, values=["选择主题", "cosmo (默认)", "flatly", "darkly"]), ttk.Combobox(tab_cfg, values=["选择字体", "更纱黑体 (默认)", "系统默认字体"]), ] } wdgt[tab_electing][0].place(relx=0.5, rely=0.35, anchor='center') wdgt[tab_electing][1].place(relx=0.5, rely=0.8, relwidth=0.25, relheight=0.15, anchor='center') wdgt[tab_electing][2].place(relx=0.01, rely=0.99, relwidth=0.12, relheight=0.08, anchor='sw') wdgt[tab_electing][3].place(relx=0.99, rely=0.99, relwidth=0.12, relheight=0.08, anchor='se') wdgt[tab_about][0].insert(tk.END, about_text,"center") wdgt[tab_about][0].insert(tk.END, LICENSE) wdgt[tab_about][0].grid(row=1, column=0, sticky=tk.N) wdgt[tab_about][0].configure(state="disabled") wdgt[tab_bat][4].current(0) wdgt[tab_bat][1].place(relx=0.5, rely=0.845, relwidth=0.25, relheight=0.13, anchor='n') wdgt[tab_bat][2].place(relx=0.99, rely=0.99, relwidth=0.3, relheight=0.08, anchor='se') wdgt[tab_bat][3].place(relx=0.99, rely=0.91, relwidth=0.3, relheight=0.08, anchor='se') wdgt[tab_bat][4].place(relx=0.01, rely=0.91, relwidth=0.3, relheight=0.08, anchor='sw') wdgt[tab_bat][5].place(relx=0.01, rely=0.99, relwidth=0.3, relheight=0.08, anchor='sw') wdgt[tab_bat][0].place(relx=0.03, rely=0.03, relwidth=0.94, relheight=0.79, anchor='nw') wdgt[tab_marking][0].place(relx=0.5, rely=1, relwidth=0.13, relheight=0.08, anchor='s') wdgt[tab_marking][1].place(relx=1, rely=1, relwidth=0.13, relheight=0.08, anchor='se') wdgt[tab_marking][2].place(relx=0, rely=1, relwidth=0.15, relheight=0.08, anchor='sw') wdgt[tab_marking][2].current(0) def marking_change_color(l): global style global marking_colormark global mk_bg global mk_fg if id(l) not in marking_colormark.keys(): marking_colormark[id(l)] = 0 style = tb.Style() style.configure(f"MarkingX{id(l)}.TButton", background=mk_bg[marking_colormark[id(l)]], foreground=mk_fg[marking_colormark[id(l)]]) style.map(f"MarkingX{id(l)}.TButton", background=[('pressed', mk_bg[marking_colormark[id(l)]]), ('active', mk_bg[marking_colormark[id(l)]])], foreground=[('pressed', mk_fg[marking_colormark[id(l)]]), ('active', mk_fg[marking_colormark[id(l)]])]) marking_colormark[id(l)] += 1 marking_colormark[id(l)]%=len(mk_fg) # 应用样式 l.config(style=f"MarkingX{id(l)}.TButton") global omember tmember = list(omember) for i in range((8 - len(member) % 8 )% 8): tmember.append({"name":"占位符"}) for i, j in enumerate(tmember): if len(j["name"]) == 2: j["name"] = " " + j["name"] + " " # 创建按钮并赋值给 l l = ttk.Button(tab_marking, text=j["name"], style="Marking.TButton") marking_buttons.append(l) # 使用 partial 将 l 作为参数传递给回调函数 marking_change_color(l) l.config(command=functools.partial(marking_change_color, l)) l.grid(column=i % 8, row=i // 8, padx=5, pady=5) wdgt[tab_filter][0].current(0) wdgt[tab_filter][0].grid(column=0,row=0) wdgt[tab_filter][1].grid(column=0,row=1,sticky=tk.W,padx=5,pady=5) wdgt[tab_filter][2].grid(column=0,row=2,sticky=tk.W,padx=5,pady=5) wdgt[tab_filter][3].place(relx=0.01, rely=1, relwidth=1, relheight=0.08, anchor='sw') wdgt[tab_filter][0].grid(column=0,row=0,sticky=tk.W,padx=5,pady=5) wdgt[tab_filter][1].grid(column=0,row=1,sticky=tk.W,padx=5,pady=5) wdgt[tab_cfg][0].grid(column=0, row=0, padx=5,pady=5,sticky=tk.W) wdgt[tab_cfg][1].grid(column=0, row=3, padx=5,pady=5,sticky=tk.W) wdgt[tab_cfg][2].grid(column=0, row=4, padx=5,pady=5,sticky=tk.W) wdgt[tab_cfg][3].grid(column=0, row=1, padx=5,pady=5,sticky=tk.W) wdgt[tab_cfg][4].grid(column=0, row=2, padx=5,pady=5,sticky=tk.W) global is_first if is_first: global default_tab_layout default_tab_layout = style.layout('TNotebook.Tab') style.layout('TNotebook.Tab', []) is_first = 0 root.protocol("WM_DELETE_WINDOW", on_closing) update_data() root.mainloop() if __name__ == "__main__": print("确保此终端支持 UTF-8 字符输出!") print(f"幸运选手α {version}") print("开放源代码: https://github.com/david-ajax/classaux") print("读取 YAML 配置文件与 GPL 协议 ", end="") with open("COPYING", encoding='utf_8') as license_file: LICENSE = license_file.read() with open("config.yaml", encoding='utf_8') as config_file: config = yaml.safe_load(config_file.read()) print(config['member']) omember = list(config['member']) print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL) #print(config) print(f"读取数据库 {config['info']['db']} ", end="") print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL) with open("db.csv", encoding='utf_8-sig') as db_file: db = list(csv.reader(db_file, delimiter=',', quotechar='"')) for i in db: if i[0] == 'NOTE': db.remove(i) # print(db) print(f"配置文件: {config['info']['name']}") member_sum = len(config['member']) print(f"成员人数: {len(config['member'])}") print(f"随机刻间隔时间: {stdtime}s") print("启动随机生成器守护线程 ", end="") random_thr = threading.Thread(target=random_gen) random_thr.start() print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL) print("初始化主题资源 ", end="") is_dark = config["darkmode"] init_theme() print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL) print("初始化 GUI ", end="") print(colorama.Fore.YELLOW + "正运行" + colorama.Style.RESET_ALL) interface() print(colorama.Fore.GREEN + "结束" + colorama.Style.RESET_ALL)