225 lines
7.8 KiB
Python
225 lines
7.8 KiB
Python
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 = [
|
||
("1. 📁 生成图片路径", self.run_generate_folders),
|
||
("2. 🤖 生成评语 (AI)", self.run_generate_comments),
|
||
("3. 📊 生成报告 (PPT)", self.run_generate_report),
|
||
("4. 📑 格式转换 (PDF)", self.run_convert_pdf),
|
||
("5. 🐂 生肖转化 (生日)", self.run_zodiac),
|
||
]
|
||
self.create_btn_group(main_content, "🛠️ 核心功能", func_btns, columns=3)
|
||
|
||
# === B组: 数据导出 ===
|
||
export_btns = [
|
||
("6. 📦 导出数据模板 (Zip)", self.run_export_data_folder),
|
||
("8. 📤 导出数据备份 (Zip)", self.run_export_data),
|
||
]
|
||
self.create_btn_group(main_content, "📦 数据管理", export_btns, columns=2)
|
||
|
||
# === C组: 系统设置 ===
|
||
system_btns = [
|
||
("7. ⚠️ 初始化系统 (重置)", self.run_initialize_project),
|
||
("9. 🚪 退出系统", 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() |