Files
growth_report/UI.py

200 lines
7.2 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
import threading
import sys
import time
import queue # 【新增】引入队列库
import re # 【新增】用于去除 ANSI 颜色代码
from loguru import logger # 【新增】需要导入 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_data_folder, initialize_project
# ==========================================
# 0. 全局配置与队列准备
# ==========================================
config = load_config("config.toml")
# 【新增】创建一个全局队列,用于存放日志消息
log_queue = queue.Queue()
def ansi_cleaner(text):
"""【辅助函数】去除 loguru 输出中的 ANSI 颜色代码 (例如 \x1b[32m)"""
ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
return ansi_escape.sub("", text)
def queue_sink(message):
"""【核心】这是一个 loguru 的 sink 回调函数
当 logger.info() 被调用时loguru 会把格式化好的消息传给这个函数
"""
# message 是一个对象,我们将其转换为字符串并放入队列
# 使用 ansi_cleaner 去掉颜色代码,否则 GUI 里会有乱码
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("700x600") # 稍微调大一点
# 设置样式
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")
# --- 1. 标题区域 ---
header_frame = ttk.Frame(root, padding="10 20 10 10")
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. 按钮功能区域 ---
btn_frame = ttk.Frame(root, padding=10)
btn_frame.pack(fill=tk.X)
buttons = [
("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),
("6. 📦 导出数据模板 (Zip)", self.run_export_data_folder),
("7. 📤 初始化系统", self.run_initialize_project),
("8. 🚪 退出系统", self.quit_app),
]
for index, (text, func) in enumerate(buttons):
btn = ttk.Button(btn_frame, text=text, command=func)
r, c = divmod(index, 2)
btn.grid(row=r, column=c, padx=10, pady=5, sticky="ew")
btn_frame.columnconfigure(0, weight=1)
btn_frame.columnconfigure(1, weight=1)
# --- 3. 日志输出区域 ---
log_frame = ttk.LabelFrame(root, text="系统实时日志", padding=10)
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 注意:这里字体设为 Consolas 或其他等宽字体,看起来更像代码日志
self.log_text = scrolledtext.ScrolledText(
log_frame, height=10, state="disabled", font=("Consolas", 9)
)
self.log_text.pack(fill=tk.BOTH, expand=True)
# 【新增】启动日志轮询
# 告诉 GUI每隔 100ms 检查一下队列里有没有新日志
self.root.after(100, self.poll_log_queue)
logger.info("GUI 初始化完成,等待指令...")
# --- 【新增】核心方法:轮询队列 ---
def poll_log_queue(self):
"""检查队列中是否有新日志,如果有则显示到界面上"""
while not log_queue.empty():
try:
# 不阻塞地获取消息
msg = log_queue.get_nowait()
# 写入 GUI
self.log_text.config(state="normal")
self.log_text.insert(tk.END, msg) # msg 已经包含了换行符
self.log_text.see(tk.END) # 自动滚动到底部
self.log_text.config(state="disabled")
except queue.Empty:
break
# 递归调用100ms 后再次执行自己
self.root.after(100, self.poll_log_queue)
# --- 辅助方法:线程包装器 ---
def run_in_thread(self, target_func, *args):
"""在单独的线程中运行函数"""
def thread_task():
try:
# 这里不需要手动 self.log 了,因为 target_func 内部的 logger 会自动触发 sink
target_func(*args)
logger.success("✅ 当前任务执行完毕。") # 使用 logger 输出成功
except Exception as e:
logger.error(f"❌ 发生错误: {str(e)}") # 使用 logger 输出错误
import traceback
logger.error(traceback.format_exc())
threading.Thread(target=thread_task, daemon=True).start()
# ==========================================
# 按钮事件 (不需要改动,逻辑都在 thread_task 里处理了)
# ==========================================
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):
# 传入 output_folder
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):
self.run_in_thread(export_data_folder)
def run_initialize_project(self):
self.run_in_thread(initialize_project)
def quit_app(self):
if messagebox.askokcancel("退出", "确定要退出系统吗?"):
self.root.destroy()
sys.exit()
# ==========================================
# 启动入口
# ==========================================
def applicationUI():
# 【核心配置】在启动 GUI 前,配置 loguru
# 1. remove() 移除默认的控制台输出(如果你想保留控制台黑窗口,可以注释掉这行)
# logger.remove()
# 2. 添加我们的自定义 sink (queue_sink)
# format 可以自定义,这里保持简单,或者尽量模拟 loguru 默认格式
logger.add(
queue_sink,
format="{time:HH:mm:ss} | {level: <8} | {message}",
level="INFO", # 只显示 INFO 及以上级别,避免 DEBUG 信息刷屏
)
root = tk.Tk()
app = ReportApp(root)
root.mainloop()