Files
growth_report/UI.py
2025-12-11 21:42:01 +08:00

225 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import messagebox
from tkinter import filedialog
import threading
import sys
import time
import queue
import re
from loguru import logger
from config.config import load_config
# 假设你的功能函数都在这里
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
# ==========================================
# 0. 全局配置与队列准备
# ==========================================
config = load_config("config.toml")
log_queue = queue.Queue()
def ansi_cleaner(text):
"""【辅助函数】去除 loguru 输出中的 ANSI 颜色代码"""
ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
return ansi_escape.sub("", text)
def queue_sink(message):
"""【核心】loguru sink 回调"""
clean_msg = ansi_cleaner(message)
log_queue.put(clean_msg)
# ==========================================
# GUI 主程序类
# ==========================================
class ReportApp:
def __init__(self, root):
self.root = root
self.root.title("🌱 尚城幼儿园成长报告助手")
self.root.geometry("720x680") # 高度稍微增加一点以容纳分组
# 设置样式
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("Sub.TLabel", font=("微软雅黑", 9), foreground="gray")
# LabelFrame 的标题样式
self.style.configure("TLabelframe.Label", font=("微软雅黑", 10, "bold"), foreground="#0055a3")
# --- 1. 标题区域 ---
header_frame = ttk.Frame(root, padding="10 15 10 5")
header_frame.pack(fill=tk.X)
ttk.Label(header_frame, text="🌱 尚城幼儿园成长报告助手", style="Title.TLabel").pack()
ttk.Label(header_frame, text="By 寒寒", style="Sub.TLabel").pack()
# --- 2. 按钮功能区域 (使用 LabelFrame 分组) ---
# 容器 Frame给四周留点白
main_content = ttk.Frame(root, padding=10)
main_content.pack(fill=tk.X)
# === A组: 核心功能 ===
func_btns = [
("📁 生成图片路径", self.run_generate_folders),
("🤖 生成评语 (AI)", self.run_generate_comments),
("📊 生成报告 (PPT)", self.run_generate_report),
("📑 格式转换 (PDF)", self.run_convert_pdf),
("🐂 生肖转化 (生日)", self.run_zodiac),
]
self.create_btn_group(main_content, "🛠️ 核心功能", func_btns, columns=3)
# === B组: 数据导出 ===
export_btns = [
("📦 导出数据模板 (Zip)", self.run_export_data_folder),
("📤 导出数据备份 (Zip)", self.run_export_data),
]
self.create_btn_group(main_content, "📦 数据管理", export_btns, columns=2)
# === C组: 系统设置 ===
system_btns = [
("⚠️ 初始化系统 (重置)", self.run_initialize_project),
("🚪 退出系统", self.quit_app),
]
self.create_btn_group(main_content, "⚙️ 系统操作", system_btns, columns=2)
# --- 3. 日志输出区域 ---
log_frame = ttk.LabelFrame(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)
# 启动日志轮询
self.root.after(100, self.poll_log_queue)
logger.info("GUI 初始化完成,等待指令...")
def create_btn_group(self, parent, title, buttons, columns=2):
"""
辅助函数:快速创建分组按钮
:param parent: 父容器
:param title: 分组标题
:param buttons: 按钮列表 [(text, func), ...]
:param columns: 每行显示几个按钮
"""
frame = ttk.LabelFrame(parent, text=title, padding=10)
frame.pack(fill=tk.X, pady=5) # 垂直堆叠
for index, (text, func) in enumerate(buttons):
# 特殊处理:如果是"初始化"或"退出",可以用不同的样式(可选,这里暂不做)
btn = ttk.Button(frame, text=text, command=func)
# 动态计算网格位置
r, c = divmod(index, columns)
btn.grid(row=r, column=c, padx=8, pady=5, sticky="ew")
# 配置列权重,让按钮自动填满宽度
for i in range(columns):
frame.columnconfigure(i, weight=1)
# --- 核心方法:轮询队列 ---
def poll_log_queue(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.poll_log_queue)
# --- 线程包装器 ---
def run_in_thread(self, target_func, *args):
def thread_task():
try:
target_func(*args)
logger.success("✅ 当前任务执行完毕。")
except Exception as e:
logger.error(f"❌ 发生错误: {str(e)}")
import traceback
logger.error(traceback.format_exc())
threading.Thread(target=thread_task, daemon=True).start()
# ==========================================
# 按钮事件 (业务逻辑保持不变)
# ==========================================
def run_generate_folders(self):
self.run_in_thread(generate_template)
def run_generate_comments(self):
self.run_in_thread(generate_comment_all)
def run_generate_report(self):
self.run_in_thread(generate_report)
def run_convert_pdf(self):
self.run_in_thread(batch_convert_folder, config["output_folder"])
def run_zodiac(self):
self.run_in_thread(generate_zodiac)
def run_export_data_folder(self):
target_folder = filedialog.askdirectory(
title="请选择导出数据保存的文件夹",
initialdir=config.get("output_folder", ".")
)
if not target_folder:
logger.warning("🚫 导出操作已取消")
return
logger.info(f"已选择保存路径: {target_folder}")
self.run_in_thread(export_templates_folder, target_folder)
def run_initialize_project(self):
self.run_in_thread(initialize_project)
def run_export_data(self):
target_folder = filedialog.askdirectory(
title="请选择导出数据保存的文件夹",
initialdir=config.get("output_folder", ".")
)
if not target_folder:
logger.warning("🚫 导出操作已取消")
return
logger.info(f"已选择保存路径: {target_folder}")
self.run_in_thread(export_data, target_folder)
def quit_app(self):
if messagebox.askokcancel("退出", "确定要退出系统吗?"):
self.root.destroy()
sys.exit()
# ==========================================
# 启动入口
# ==========================================
def applicationUI():
logger.add(
queue_sink,
format="{time:HH:mm:ss} | {level: <8} | {message}",
level="INFO",
)
root = tk.Tk()
app = ReportApp(root)
root.mainloop()
if __name__ == "__main__":
applicationUI()