161 lines
6.1 KiB
Python
161 lines
6.1 KiB
Python
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() |