From 016e392524dd1d5ddb0e543084ebeeb4180ad5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=92=E5=AF=92?= <2596194220@qq.com> Date: Sat, 24 Jan 2026 23:53:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E5=8E=BB=E9=99=A4=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +-- main_nicegui.py => main.py | 0 main.pyw | 81 ------------- start_app.bat | 4 +- ui/app_window.py | 236 ------------------------------------- 5 files changed, 6 insertions(+), 332 deletions(-) rename main_nicegui.py => main.py (100%) delete mode 100644 main.pyw delete mode 100644 ui/app_window.py diff --git a/README.md b/README.md index d5e30c7..6d32b60 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - 🤖 **AI评语**: 智能生成个性化、治愈系风格的幼儿评语 - 🖼️ **图文并茂**: 支持个人照片、活动照片、班级合影的自动替换 - 📄 **格式转换**: 批量PPT转PDF,便于分发和存档 -- 🎨 **多界面**: 提供tkinter图形界面和NiceGUI现代Web界面,满足不同用户需求 +- 🎨 **现代界面**: 提供NiceGUI现代Web界面,操作直观友好 - 🐲 **生肖计算**: 根据生日自动计算生肖信息 - 📦 **模板导出**: 生成标准化数据模板,快速上手 - 🔤 **字体安装**: 自动检测和安装所需字体文件 @@ -28,7 +28,6 @@ - **comtypes**: PowerPoint转PDF功能 - **rich**: 美化命令行界面 - **loguru**: 日志记录 -- **tkinter**: 图形用户界面 - **nicegui**: 现代Web界面 - **tomli**: 配置文件解析 @@ -72,10 +71,10 @@ pip install -r requirements.txt ### 4. 运行程序 -#### 图形界面(推荐,tkinter界面) +#### NiceGUI界面(推荐,现代Web界面) ```bash -python main.pyw +python main.py ``` 或直接运行: @@ -84,12 +83,6 @@ python main.pyw start_app.bat ``` -#### NiceGUI界面(现代Web界面) - -```bash -python main_nicegui.py -``` - ## 📖 使用指南 ### 功能模块 @@ -156,8 +149,7 @@ signature_height = 720000 # 高度 ``` growth_report/ -├── main.pyw # Windows图形界面启动文件 -├── main_nicegui.py # NiceGUI界面入口 +├── main.py # NiceGUI界面入口 ├── config.toml # 项目配置文件 ├── pyproject.toml # 项目依赖配置 ├── start_app.bat # 启动脚本 @@ -166,7 +158,6 @@ growth_report/ ├── config/ │ ├── config.py # 配置加载工具 ├── ui/ -│ ├── app_window.py # tkinter图形界面 │ ├── assets/ │ │ ├── icon.ico # 应用图标 │ │ └── style.css # 样式文件 diff --git a/main_nicegui.py b/main.py similarity index 100% rename from main_nicegui.py rename to main.py diff --git a/main.pyw b/main.pyw deleted file mode 100644 index 56ca575..0000000 --- a/main.pyw +++ /dev/null @@ -1,81 +0,0 @@ -import sys -import tkinter as tk - -from loguru import logger - -from ui.app_window import ReportApp -from utils.log_handler import setup_logging - -# 全局变量,用于判断日志是否已初始化 -LOGGING_INITIALIZED = False - - -# --- 全局错误处理 --- -def handle_exception(exc_type, exc_value, exc_traceback): - """ - 捕获未被 try/except 块处理的全局异常(如线程崩溃)。 - """ - if exc_type is KeyboardInterrupt: - sys.__excepthook__(exc_type, exc_value, exc_traceback) - return - - # 尝试使用 loguru 记录 - if LOGGING_INITIALIZED: - logger.error("捕获到未处理的全局异常:", exc_info=(exc_type, exc_value, exc_traceback)) - else: - # 如果日志系统未初始化,直接打印到标准错误流,确保用户看到 - print("FATAL ERROR (Log Not Initialized):", file=sys.stderr) - import traceback - traceback.print_exception(exc_type, exc_value, exc_traceback, file=sys.stderr) - - -sys.excepthook = handle_exception - - -# -------------------- - - -def create_main_window(): - global LOGGING_INITIALIZED - - # 顶级 try 块,捕获日志初始化阶段的错误 - try: - # 1. 初始化日志 - setup_logging() - LOGGING_INITIALIZED = True - logger.info("正在启动应用程序...") - - # 2. 启动 UI - root = tk.Tk() - - # 这一行可以设置图标 (如果有 icon.ico 文件) - # root.iconbitmap(os.path.join(os.path.dirname(__file__), "public", "icon.ico")) - - # 确保 ReportApp 实例化时不会出现路径错误 - app = ReportApp(root) - - # 3. 进入主循环 - root.mainloop() - - except Exception as e: - # 如果日志系统已启动,使用 logger 记录 - if LOGGING_INITIALIZED: - logger.error(f"应用程序启动/主循环出错: {e}", exc_info=True) - else: - # 如果日志系统未初始化,直接打印到控制台 - print(f"FATAL STARTUP ERROR: {e}", file=sys.stderr) - import traceback - traceback.print_exc(file=sys.stderr) - - # 确保窗口被销毁 - if 'root' in locals() and root: - root.destroy() - - # 非窗口模式下,在启动错误时等待用户查看 - if not getattr(sys, 'frozen', False) or not any(arg in sys.argv for arg in ('--windowed', '-w')): - input("按任意键退出...") - sys.exit(1) - - -if __name__ == "__main__": - create_main_window() \ No newline at end of file diff --git a/start_app.bat b/start_app.bat index 1e3d2c1..075a306 100644 --- a/start_app.bat +++ b/start_app.bat @@ -37,10 +37,10 @@ echo [INFO] 正在拉起主程序... echo --------------------------------------------------- :: ======================================================= -:: 【关键修改】路径改为根目录的 main.pyw +:: 【关键修改】路径改为根目录的 main.py :: 使用 start 命令启动,这样黑色的 CMD 窗口可以随后立即关闭 :: ======================================================= -start "" uv run main.pyw +start "" uv run main.py :: 等待 1 秒确保启动 timeout /t 1 >nul diff --git a/ui/app_window.py b/ui/app_window.py deleted file mode 100644 index 0d34f6f..0000000 --- a/ui/app_window.py +++ /dev/null @@ -1,236 +0,0 @@ -import os -import time -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.font_utils import install_fonts_from_directory -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 - - # 尝试初始化 UI - try: - self._setup_ui() - except Exception as e: - logger.critical(f"UI 初始化失败: {e}", exc_info=True) - messagebox.showerror("致命错误", f"界面初始化失败,请检查日志。\n错误: {e}") - self.root.destroy() - sys.exit(1) - - # 尝试初始化项目资源 - try: - self.init_project() - except Exception as e: - logger.critical(f"项目资源初始化失败: {e}", exc_info=True) - messagebox.showerror("致命错误", f"项目资源初始化失败,请检查日志。\n错误: {e}") - self.root.destroy() - sys.exit(1) - - 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 15 10 5") - main_content.pack(fill=tk.X) - - # === 进度条区域 === - progress_frame = ttk.Frame(self.root, padding="10 15 10 5") - progress_frame.pack(fill=tk.X, pady=(0, 10)) - - # 进度条 Label - self.progress_label = ttk.Label(progress_frame, text="⛳ 任务进度: 待命", font=("微软雅黑", 10)) - self.progress_label.pack(fill=tk.X, pady=(0, 2)) - - # 进度条 - self.progressbar = ttk.Progressbar(progress_frame, orient="horizontal", mode="determinate") - self.progressbar.pack(fill=tk.X, expand=True) - - # === 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 15 10 5") - 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 init_project(self): - # 1. 资源准备 - if install_fonts_from_directory(config["fonts_dir"]): - logger.info("等待系统识别新安装的字体...") - time.sleep(2) - # 2. 创建输出文件夹 - os.makedirs(config["output_folder"], exist_ok=True) - logger.success("项目初始化完成.....") - - # --- 任务运行核心逻辑 --- - def run_task(self, target_func, *args, **kwargs): - if self.is_running: - messagebox.showwarning("忙碌中", "请先等待当前任务完成或点击【停止当前任务】") - return - - self.stop_event.clear() - self.is_running = True - - # 将进度更新方法作为参数传入 - kwargs['progress_callback'] = self.update_progress - - 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("系统准备就绪.....") - self.reset_progress() # 重置进度条 - - 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() - - # --- 进度条更新(实现线程安全更新) --- - def update_progress(self, current, total, task_name="任务"): - """ - 线程安全地更新进度条和标签 - :param current: 当前完成的项目数 - :param total: 总项目数 - :param task_name: 当前任务名称 - """ - if total <= 0: - # 重置进度条 - self.progressbar['value'] = 0 - self.progress_label.config(text=f"任务进度: {task_name} 完成或待命") - return - - percentage = int((current / total) * 100) - display_text = f"{task_name}: {current}/{total} ({percentage}%)" - - # 使用 after 确保在主线程中更新 UI - self.root.after(0, self._set_progress_ui, percentage, display_text) - - def _set_progress_ui(self, percentage, display_text): - """实际更新 UI 的私有方法""" - self.progressbar['value'] = percentage - self.progress_label.config(text=display_text) - - def reset_progress(self): - """任务结束后重置进度条""" - self.root.after(0, self._set_progress_ui, 0, "任务进度: 就绪")