fix:优化项目结构,去除历史代码
This commit is contained in:
17
README.md
17
README.md
@@ -13,7 +13,7 @@
|
|||||||
- 🤖 **AI评语**: 智能生成个性化、治愈系风格的幼儿评语
|
- 🤖 **AI评语**: 智能生成个性化、治愈系风格的幼儿评语
|
||||||
- 🖼️ **图文并茂**: 支持个人照片、活动照片、班级合影的自动替换
|
- 🖼️ **图文并茂**: 支持个人照片、活动照片、班级合影的自动替换
|
||||||
- 📄 **格式转换**: 批量PPT转PDF,便于分发和存档
|
- 📄 **格式转换**: 批量PPT转PDF,便于分发和存档
|
||||||
- 🎨 **多界面**: 提供tkinter图形界面和NiceGUI现代Web界面,满足不同用户需求
|
- 🎨 **现代界面**: 提供NiceGUI现代Web界面,操作直观友好
|
||||||
- 🐲 **生肖计算**: 根据生日自动计算生肖信息
|
- 🐲 **生肖计算**: 根据生日自动计算生肖信息
|
||||||
- 📦 **模板导出**: 生成标准化数据模板,快速上手
|
- 📦 **模板导出**: 生成标准化数据模板,快速上手
|
||||||
- 🔤 **字体安装**: 自动检测和安装所需字体文件
|
- 🔤 **字体安装**: 自动检测和安装所需字体文件
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
- **comtypes**: PowerPoint转PDF功能
|
- **comtypes**: PowerPoint转PDF功能
|
||||||
- **rich**: 美化命令行界面
|
- **rich**: 美化命令行界面
|
||||||
- **loguru**: 日志记录
|
- **loguru**: 日志记录
|
||||||
- **tkinter**: 图形用户界面
|
|
||||||
- **nicegui**: 现代Web界面
|
- **nicegui**: 现代Web界面
|
||||||
- **tomli**: 配置文件解析
|
- **tomli**: 配置文件解析
|
||||||
|
|
||||||
@@ -72,10 +71,10 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
### 4. 运行程序
|
### 4. 运行程序
|
||||||
|
|
||||||
#### 图形界面(推荐,tkinter界面)
|
#### NiceGUI界面(推荐,现代Web界面)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python main.pyw
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
或直接运行:
|
或直接运行:
|
||||||
@@ -84,12 +83,6 @@ python main.pyw
|
|||||||
start_app.bat
|
start_app.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
#### NiceGUI界面(现代Web界面)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python main_nicegui.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📖 使用指南
|
## 📖 使用指南
|
||||||
|
|
||||||
### 功能模块
|
### 功能模块
|
||||||
@@ -156,8 +149,7 @@ signature_height = 720000 # 高度
|
|||||||
|
|
||||||
```
|
```
|
||||||
growth_report/
|
growth_report/
|
||||||
├── main.pyw # Windows图形界面启动文件
|
├── main.py # NiceGUI界面入口
|
||||||
├── main_nicegui.py # NiceGUI界面入口
|
|
||||||
├── config.toml # 项目配置文件
|
├── config.toml # 项目配置文件
|
||||||
├── pyproject.toml # 项目依赖配置
|
├── pyproject.toml # 项目依赖配置
|
||||||
├── start_app.bat # 启动脚本
|
├── start_app.bat # 启动脚本
|
||||||
@@ -166,7 +158,6 @@ growth_report/
|
|||||||
├── config/
|
├── config/
|
||||||
│ ├── config.py # 配置加载工具
|
│ ├── config.py # 配置加载工具
|
||||||
├── ui/
|
├── ui/
|
||||||
│ ├── app_window.py # tkinter图形界面
|
|
||||||
│ ├── assets/
|
│ ├── assets/
|
||||||
│ │ ├── icon.ico # 应用图标
|
│ │ ├── icon.ico # 应用图标
|
||||||
│ │ └── style.css # 样式文件
|
│ │ └── style.css # 样式文件
|
||||||
|
|||||||
81
main.pyw
81
main.pyw
@@ -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()
|
|
||||||
@@ -37,10 +37,10 @@ echo [INFO] 正在拉起主程序...
|
|||||||
echo ---------------------------------------------------
|
echo ---------------------------------------------------
|
||||||
|
|
||||||
:: =======================================================
|
:: =======================================================
|
||||||
:: 【关键修改】路径改为根目录的 main.pyw
|
:: 【关键修改】路径改为根目录的 main.py
|
||||||
:: 使用 start 命令启动,这样黑色的 CMD 窗口可以随后立即关闭
|
:: 使用 start 命令启动,这样黑色的 CMD 窗口可以随后立即关闭
|
||||||
:: =======================================================
|
:: =======================================================
|
||||||
start "" uv run main.pyw
|
start "" uv run main.py
|
||||||
|
|
||||||
:: 等待 1 秒确保启动
|
:: 等待 1 秒确保启动
|
||||||
timeout /t 1 >nul
|
timeout /t 1 >nul
|
||||||
|
|||||||
236
ui/app_window.py
236
ui/app_window.py
@@ -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, "任务进度: 就绪")
|
|
||||||
Reference in New Issue
Block a user