a13rollcall/wizard_launcher.py
2026-09-26 20:17:31 +08:00

351 行
15 KiB
Python

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

import os
import sys
import subprocess
import configparser
import tkinter as tk
from tkinter import ttk, messagebox
from tkinter.font import Font
APP_EXE = "A13课堂点名系统.exe"
WIZARD_INI = "wizard_record.ini"
STUDENT_FILE = "students.txt"
TEXT_FILE = "texts.txt"
MAIN_PY = "main_qt.py"
if getattr(sys, 'frozen', False):
BASE_DIR = os.path.dirname(sys.executable)
else:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
INI_PATH = os.path.join(BASE_DIR, WIZARD_INI)
STUDENT_PATH = os.path.join(BASE_DIR, STUDENT_FILE)
TEXT_PATH = os.path.join(BASE_DIR, TEXT_FILE)
class WizardApp:
def __init__(self, root):
self.root = root
self.root.title("A13 课堂点名系统 - 配置向导")
self.root.geometry("640x820")
self.root.resizable(False, False)
self.root.configure(bg="#f3f3f3")
try:
self.root.iconbitmap(os.path.join(BASE_DIR, "app.ico"))
except Exception:
pass
self.current_step = 0
self.class_name = tk.StringVar(value="A13")
self.steps = [
self._page_welcome,
self._page_class,
self._page_students,
self._page_texts,
self._page_finish,
]
self._setup_style()
self._build_ui()
self._show_step()
def _setup_style(self):
style = ttk.Style()
style.theme_use("clam")
style.configure("Wiz.TFrame", background="#f3f3f3")
style.configure("Title.TLabel", font=("微软雅黑", 20, "bold"), foreground="#0067c0", background="#f3f3f3")
style.configure("Subtitle.TLabel", font=("微软雅黑", 11), foreground="#666666", background="#f3f3f3")
style.configure("Content.TLabel", font=("微软雅黑", 11), foreground="#333333", background="#f3f3f3")
style.configure("Card.TFrame", background="#ffffff", relief="solid", borderwidth=1)
style.configure("Primary.TButton", font=("微软雅黑", 11, "bold"), padding=10)
style.configure("Nav.TButton", font=("微软雅黑", 10), padding=8)
def _build_ui(self):
main = ttk.Frame(self.root, style="Wiz.TFrame")
main.pack(fill="both", expand=True, padx=0, pady=0)
header = tk.Frame(main, bg="#0067c0", height=60)
header.pack(fill="x")
header.pack_propagate(False)
header_title = tk.Label(header, text="A13 课堂点名系统", font=("微软雅黑", 16, "bold"), fg="white", bg="#0067c0")
header_title.pack(side="left", padx=24, pady=14)
self.step_label = tk.Label(header, text="", font=("微软雅黑", 11), fg="#cce8ff", bg="#0067c0")
self.step_label.pack(side="right", padx=24, pady=18)
self.content = tk.Frame(main, bg="#f3f3f3")
self.content.pack(fill="both", expand=True, padx=32, pady=24)
nav = tk.Frame(main, bg="#f3f3f3", height=60)
nav.pack(fill="x", side="bottom")
nav.pack_propagate(False)
self.prev_btn = tk.Button(nav, text="上一步", font=("微软雅黑", 10), bg="#ffffff", fg="#333333", relief="solid", bd=1, padx=20, pady=6, command=self._prev)
self.prev_btn.pack(side="left", padx=24, pady=12)
self.next_btn = tk.Button(nav, text="下一步", font=("微软雅黑", 10, "bold"), bg="#0067c0", fg="white", relief="flat", padx=24, pady=6, command=self._next)
self.next_btn.pack(side="right", padx=24, pady=12)
def _clear_content(self):
for w in self.content.winfo_children():
w.destroy()
def _show_step(self):
self._clear_content()
self.step_label.config(text=f"第 {self.current_step + 1} / {len(self.steps)} 步")
self.steps[self.current_step]()
self.prev_btn.config(state="normal" if self.current_step > 0 else "disabled")
if self.current_step == len(self.steps) - 1:
self.next_btn.config(text="完成")
else:
self.next_btn.config(text="下一步")
def _page_welcome(self):
title = tk.Label(self.content, text="欢迎使用!", font=("微软雅黑", 22, "bold"), fg="#0067c0", bg="#f3f3f3")
title.pack(pady=(20, 8))
subtitle = tk.Label(self.content, text="A13 智能抽取引擎 V6.8", font=("微软雅黑", 12), fg="#666666", bg="#f3f3f3")
subtitle.pack(pady=(0, 24))
card = tk.Frame(self.content, bg="white", relief="solid", bd=1)
card.pack(fill="x", padx=20, pady=10)
card_inner = tk.Frame(card, bg="white")
card_inner.pack(fill="x", padx=20, pady=16)
features = [
("🎲", "智能随机抽取", "支持自动/手动模式,不重复抽取"),
("📖", "课文联动", "抽取学生同时展示对应课文"),
("📊", "积分统计", "自动记录背诵状态,生成排行榜"),
("💬", "回声洞", "匿名留言功能,倾听每一个心声"),
]
for icon, title, desc in features:
row = tk.Frame(card_inner, bg="white")
row.pack(fill="x", pady=6)
icon_label = tk.Label(row, text=icon, font=("微软雅黑", 18), bg="white")
icon_label.pack(side="left", padx=(0, 12))
text_col = tk.Frame(row, bg="white")
text_col.pack(side="left", fill="x", expand=True)
t_label = tk.Label(text_col, text=title, font=("微软雅黑", 11, "bold"), fg="#333333", bg="white", anchor="w")
t_label.pack(fill="x")
d_label = tk.Label(text_col, text=desc, font=("微软雅黑", 10), fg="#888888", bg="white", anchor="w")
d_label.pack(fill="x")
tip = tk.Label(self.content, text="点击「下一步」开始配置你的班级点名系统", font=("微软雅黑", 10), fg="#999999", bg="#f3f3f3")
tip.pack(pady=20)
def _page_class(self):
title = tk.Label(self.content, text="设置班级名称", font=("微软雅黑", 20, "bold"), fg="#0067c0", bg="#f3f3f3")
title.pack(pady=(20, 8))
subtitle = tk.Label(self.content, text="窗口标题将显示为「某某班点名程序」", font=("微软雅黑", 11), fg="#666666", bg="#f3f3f3")
subtitle.pack(pady=(0, 24))
card = tk.Frame(self.content, bg="white", relief="solid", bd=1)
card.pack(fill="x", padx=40, pady=10)
card_inner = tk.Frame(card, bg="white")
card_inner.pack(fill="x", padx=24, pady=24)
label = tk.Label(card_inner, text="班级名称:", font=("微软雅黑", 12), fg="#333333", bg="white")
label.pack(anchor="w", pady=(0, 8))
entry = tk.Entry(card_inner, textvariable=self.class_name, font=("微软雅黑", 14), relief="solid", bd=1)
entry.pack(fill="x", ipady=6)
entry.focus_set()
preview = tk.Label(card_inner, text="", font=("微软雅黑", 11), fg="#0067c0", bg="white")
preview.pack(anchor="w", pady=(12, 0))
def update_preview(*args):
name = self.class_name.get().strip() or "A13"
preview.config(text=f"预览:{name}班点名程序")
self.class_name.trace_add("write", update_preview)
update_preview()
tip = tk.Label(self.content, text="提示:可以在设置中随时修改班级名称", font=("微软雅黑", 10), fg="#999999", bg="#f3f3f3")
tip.pack(pady=20)
def _page_students(self):
title = tk.Label(self.content, text="学生名单", font=("微软雅黑", 20, "bold"), fg="#0067c0", bg="#f3f3f3")
title.pack(pady=(20, 8))
subtitle = tk.Label(self.content, text="配置学生名单文件格式", font=("微软雅黑", 11), fg="#666666", bg="#f3f3f3")
subtitle.pack(pady=(0, 20))
card = tk.Frame(self.content, bg="white", relief="solid", bd=1)
card.pack(fill="both", expand=True, padx=20, pady=10)
card_inner = tk.Frame(card, bg="white")
card_inner.pack(fill="both", expand=True, padx=20, pady=16)
format_label = tk.Label(card_inner, text="文件格式:students.txt", font=("微软雅黑", 12, "bold"), fg="#333333", bg="white")
format_label.pack(anchor="w", pady=(0, 8))
example = tk.Text(card_inner, height=6, font=("Consolas", 11), relief="solid", bd=1, bg="#fafafa")
example.pack(fill="x", pady=(0, 12))
example.insert("1.0", "张三\n李四\n王五\n赵六\n...")
example.config(state="disabled")
tips = [
"• 每行一个学生姓名",
"• 支持带序号格式(如「1 张三」)",
"• 文件编码请使用 UTF-8",
"• 点击下方按钮可用记事本编辑",
]
for tip in tips:
t = tk.Label(card_inner, text=tip, font=("微软雅黑", 10), fg="#666666", bg="white", anchor="w")
t.pack(fill="x", pady=2)
btn_row = tk.Frame(card_inner, bg="white")
btn_row.pack(fill="x", pady=(12, 0))
edit_btn = tk.Button(btn_row, text="📝 编辑学生名单", font=("微软雅黑", 10), bg="#0067c0", fg="white", relief="flat", padx=16, pady=6, command=lambda: self._edit_file(STUDENT_PATH))
edit_btn.pack(side="left")
def _page_texts(self):
title = tk.Label(self.content, text="课文库", font=("微软雅黑", 20, "bold"), fg="#0067c0", bg="#f3f3f3")
title.pack(pady=(20, 8))
subtitle = tk.Label(self.content, text="配置课文库文件格式", font=("微软雅黑", 11), fg="#666666", bg="#f3f3f3")
subtitle.pack(pady=(0, 20))
card = tk.Frame(self.content, bg="white", relief="solid", bd=1)
card.pack(fill="both", expand=True, padx=20, pady=10)
card_inner = tk.Frame(card, bg="white")
card_inner.pack(fill="both", expand=True, padx=20, pady=16)
format_label = tk.Label(card_inner, text="文件格式:texts.txt", font=("微软雅黑", 12, "bold"), fg="#333333", bg="white")
format_label.pack(anchor="w", pady=(0, 8))
example = tk.Text(card_inner, height=6, font=("Consolas", 11), relief="solid", bd=1, bg="#fafafa")
example.pack(fill="x", pady=(0, 12))
example.insert("1.0", "【第一课 春】\n春天来了,万物复苏...\n\n【第二课 夏】\n夏天到了,烈日炎炎...")
example.config(state="disabled")
tips = [
"• 用【】包裹课文标题",
"• 标题下方为课文内容,空行分隔段落",
"• 支持多篇课文,抽取时自动联动",
"• 点击下方按钮可用记事本编辑",
]
for tip in tips:
t = tk.Label(card_inner, text=tip, font=("微软雅黑", 10), fg="#666666", bg="white", anchor="w")
t.pack(fill="x", pady=2)
btn_row = tk.Frame(card_inner, bg="white")
btn_row.pack(fill="x", pady=(12, 0))
edit_btn = tk.Button(btn_row, text="📝 编辑课文库", font=("微软雅黑", 10), bg="#0067c0", fg="white", relief="flat", padx=16, pady=6, command=lambda: self._edit_file(TEXT_PATH))
edit_btn.pack(side="left")
def _page_finish(self):
title = tk.Label(self.content, text="配置完成!", font=("微软雅黑", 22, "bold"), fg="#107c10", bg="#f3f3f3")
title.pack(pady=(30, 8))
class_name = self.class_name.get().strip() or "A13"
subtitle = tk.Label(self.content, text=f"{class_name}班点名程序已准备就绪", font=("微软雅黑", 12), fg="#666666", bg="#f3f3f3")
subtitle.pack(pady=(0, 24))
card = tk.Frame(self.content, bg="white", relief="solid", bd=1)
card.pack(fill="x", padx=40, pady=10)
card_inner = tk.Frame(card, bg="white")
card_inner.pack(fill="x", padx=24, pady=20)
summary = [
("班级名称", class_name),
("学生名单", STUDENT_FILE),
("课文库", TEXT_FILE),
("引擎版本", "A13 V6.8"),
]
for label, value in summary:
row = tk.Frame(card_inner, bg="white")
row.pack(fill="x", pady=4)
l = tk.Label(row, text=label + ":", font=("微软雅黑", 11), fg="#888888", bg="white", width=10, anchor="e")
l.pack(side="left")
v = tk.Label(row, text=value, font=("微软雅黑", 11, "bold"), fg="#333333", bg="white")
v.pack(side="left", padx=(8, 0))
tip = tk.Label(self.content, text="点击「完成」启动点名程序,首次启动将显示操作引导", font=("微软雅黑", 10), fg="#999999", bg="#f3f3f3")
tip.pack(pady=20)
def _edit_file(self, filepath):
if not os.path.exists(filepath):
try:
with open(filepath, "w", encoding="utf-8") as f:
f.write("")
except Exception:
pass
try:
subprocess.Popen(["notepad.exe", filepath])
except Exception as e:
messagebox.showerror("错误", f"无法打开记事本:{e}")
def _prev(self):
if self.current_step > 0:
self.current_step -= 1
self._show_step()
def _next(self):
if self.current_step < len(self.steps) - 1:
self.current_step += 1
self._show_step()
else:
self._finish()
def _finish(self):
class_name = self.class_name.get().strip() or "A13"
try:
cfg = configparser.ConfigParser()
cfg["status"] = {"finished": "True"}
cfg["app"] = {"class_name": class_name}
with open(INI_PATH, "w", encoding="utf-8") as f:
cfg.write(f)
except Exception as e:
messagebox.showerror("错误", f"写入配置失败:{e}")
return
try:
config_path = os.path.join(BASE_DIR, "config.json")
import json
config = {}
if os.path.exists(config_path):
try:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
except Exception:
pass
if "app" not in config:
config["app"] = {}
config["app"]["class_name"] = class_name
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
except Exception:
pass
self.root.destroy()
exe_path = os.path.join(BASE_DIR, APP_EXE)
py_path = os.path.join(BASE_DIR, MAIN_PY)
try:
if os.path.exists(exe_path):
subprocess.Popen([exe_path, "--show-tip"], cwd=BASE_DIR)
elif os.path.exists(py_path):
subprocess.Popen([sys.executable, py_path, "--show-tip"], cwd=BASE_DIR)
except Exception as e:
print(f"启动主程序失败: {e}")
def generate_sample_files():
if not os.path.exists(STUDENT_PATH):
try:
with open(STUDENT_PATH, "w", encoding="utf-8") as f:
f.write("张三\n李四\n王五\n赵六\n钱七\n孙八\n周九\n吴十\n")
except Exception:
pass
if not os.path.exists(TEXT_PATH):
try:
with open(TEXT_PATH, "w", encoding="utf-8") as f:
f.write("【第一课 春】\n春天来了,万物复苏。\n小草从土里探出头来,\n花儿也露出了笑脸。\n\n【第二课 夏】\n夏天到了,烈日炎炎。\n知了在树上不停地叫着,\n小朋友们在河边玩耍。\n\n【第三课 秋】\n秋天来了,树叶变黄。\n一片片叶子从树上落下,\n像一只只蝴蝶在飞舞。\n")
except Exception:
pass
def main():
generate_sample_files()
root = tk.Tk()
app = WizardApp(root)
root.mainloop()
if __name__ == "__main__":
main()