fix:修复一些BUG

This commit is contained in:
2025-12-11 17:52:39 +08:00
parent 4a5672ee62
commit ed4b324dba
11 changed files with 356 additions and 162 deletions

154
UI.py
View File

@@ -2,13 +2,14 @@ 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 # 【新增】用于去除 ANSI 颜色代码
import queue
import re
from loguru import logger # 【新增】需要导入 logger 来配置它
from loguru import logger
from config.config import load_config
# 假设你的功能函数都在这里
@@ -19,29 +20,23 @@ from utils.generate_utils import (
generate_report,
generate_zodiac,
)
from utils.file_utils import export_data_folder, initialize_project
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 颜色代码 (例如 \x1b[32m)"""
"""【辅助函数】去除 loguru 输出中的 ANSI 颜色代码"""
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 里会有乱码
"""【核心】loguru sink 回调"""
clean_msg = ansi_cleaner(message)
log_queue.put(clean_msg)
@@ -52,103 +47,118 @@ def queue_sink(message):
class ReportApp:
def __init__(self, root):
self.root = root
self.root.title("🌱 幼儿园成长报告助手")
self.root.geometry("700x600") # 稍微调大一点
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("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 20 10 10")
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="🌱 尚城幼儿园成长报告助手", 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)
# --- 2. 按钮功能区域 (使用 LabelFrame 分组) ---
# 容器 Frame给四周留点白
main_content = ttk.Frame(root, padding=10)
main_content.pack(fill=tk.X)
buttons = [
# === 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),
("6. 📦 导出数据模板 (Zip)", self.run_export_data_folder),
("7. 📤 初始化系统", self.run_initialize_project),
("8. 🚪 退出系统", self.quit_app),
]
self.create_btn_group(main_content, "🛠️ 核心功能", func_btns, columns=3)
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")
# === 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)
btn_frame.columnconfigure(0, weight=1)
btn_frame.columnconfigure(1, weight=1)
# === 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=10)
log_frame = ttk.LabelFrame(root, text="📝 系统实时日志", padding=10)
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 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 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()
# 写入 GUI
self.log_text.config(state="normal")
self.log_text.insert(tk.END, msg) # msg 已经包含了换行符
self.log_text.see(tk.END) # 自动滚动到底部
self.log_text.insert(tk.END, 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 输出成功
logger.success("✅ 当前任务执行完毕。")
except Exception as e:
logger.error(f"❌ 发生错误: {str(e)}") # 使用 logger 输出错误
logger.error(f"❌ 发生错误: {str(e)}")
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)
@@ -160,18 +170,36 @@ class ReportApp:
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)
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()
@@ -182,16 +210,10 @@ class ReportApp:
# 启动入口
# ==========================================
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 信息刷屏
level="INFO",
)
root = tk.Tk()