import tkinter as tk from tkinter import ttk, scrolledtext, messagebox, filedialog import threading import queue import sys from loguru import logger from config.config import load_config from utils.log_handler import log_queue # 导入业务逻辑 from utils.generate_utils import ( generate_template, generate_comment_all, batch_convert_folder, generate_report, generate_zodiac, ) from utils.file_utils import export_templates_folder, initialize_project, export_data config = load_config("config.toml") class ReportApp: def __init__(self, root): self.root = root self.root.title("🌱 尚城幼儿园成长报告助手") self.root.geometry("720x760") # 线程控制 self.stop_event = threading.Event() self.is_running = False self._setup_ui() self._start_log_polling() def _setup_ui(self): # 样式配置 self.style = ttk.Style() self.style.theme_use("clam") self.style.configure("TButton", font=("微软雅黑", 10), padding=5) self.style.configure("Title.TLabel", font=("微软雅黑", 16, "bold"), foreground="#2E8B57") self.style.configure("Stop.TButton", foreground="red", font=("微软雅黑", 10, "bold")) # 1. 标题 header = ttk.Frame(self.root, padding="10 15 10 5") header.pack(fill=tk.X) ttk.Label(header, text="🌱 尚城幼儿园成长报告助手", style="Title.TLabel").pack() ttk.Label(header, text="By 寒寒 | 这里的每一份评语都充满爱意", font=("微软雅黑", 9), foreground="gray").pack() # 2. 功能区容器 main_content = ttk.Frame(self.root, padding=10) main_content.pack(fill=tk.X) # === A组: 核心功能 === self._create_btn_group(main_content, "🛠️ 核心功能", [ ("📁 生成图片路径", lambda: self.run_task(generate_template)), ("🤖 生成评语 (AI)", lambda: self.run_task(generate_comment_all)), ("📊 生成报告 (PPT)", lambda: self.run_task(generate_report)), ("📑 格式转换 (PDF)", lambda: self.run_task(batch_convert_folder, config.get("output_folder"))), ("🐂 生肖转化 (生日)", lambda: self.run_task(generate_zodiac)), ], columns=3) # === B组: 数据管理 === self._create_btn_group(main_content, "📦 数据管理", [ ("📦 导出模板 (Zip)", self.run_export_template), ("📤 导出备份 (Zip)", self.run_export_data), ], columns=2) # === C组: 系统操作 (含停止按钮) === self._create_btn_group(main_content, "⚙️ 系统操作", [ ("⛔ 停止当前任务", self.stop_current_task), ("⚠️ 初始化系统", self.run_init), ("🚪 退出系统", self.quit_app), ], columns=3, special_styles={"⛔ 停止当前任务": "Stop.TButton"}) # 3. 日志区 log_frame = ttk.LabelFrame(self.root, text="📝 系统实时日志", padding=10) log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) self.log_text = scrolledtext.ScrolledText(log_frame, height=10, state="disabled", font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) def _create_btn_group(self, parent, title, buttons, columns=2, special_styles=None): frame = ttk.LabelFrame(parent, text=title, padding=10) frame.pack(fill=tk.X, pady=5) special_styles = special_styles or {} for i, (text, func) in enumerate(buttons): style = special_styles.get(text, "TButton") btn = ttk.Button(frame, text=text, command=func, style=style) r, c = divmod(i, columns) btn.grid(row=r, column=c, padx=5, pady=5, sticky="ew") for i in range(columns): frame.columnconfigure(i, weight=1) def _start_log_polling(self): while not log_queue.empty(): try: msg = log_queue.get_nowait() self.log_text.config(state="normal") self.log_text.insert(tk.END, msg) self.log_text.see(tk.END) self.log_text.config(state="disabled") except queue.Empty: break self.root.after(100, self._start_log_polling) # --- 任务运行核心逻辑 --- def run_task(self, target_func, *args, **kwargs): if self.is_running: messagebox.showwarning("忙碌中", "请先等待当前任务完成或点击【停止当前任务】") return self.stop_event.clear() self.is_running = True def thread_worker(): try: # 尝试传入 stop_event try: target_func(*args, stop_event=self.stop_event, **kwargs) except TypeError: # 如果旧函数不支持 stop_event,则普通运行 target_func(*args, **kwargs) except Exception as e: logger.error(f"任务出错: {e}") import traceback logger.error(traceback.format_exc()) finally: self.is_running = False logger.info("--- 就绪 ---") threading.Thread(target=thread_worker, daemon=True).start() def stop_current_task(self): if not self.is_running: return if messagebox.askyesno("确认", "确定要中断当前任务吗?"): self.stop_event.set() logger.warning("正在发送停止信号...") # --- 具体按钮事件 --- def run_export_template(self): path = filedialog.askdirectory() if path: self.run_task(export_templates_folder, path) def run_export_data(self): path = filedialog.askdirectory() if path: self.run_task(export_data, path) def run_init(self): if messagebox.askokcancel("警告", "确定重置系统吗?数据将丢失!"): self.run_task(initialize_project) def quit_app(self): if self.is_running: messagebox.showwarning("提示", "请先停止任务") return self.root.destroy() sys.exit()