diff --git a/main.py b/main.py
index 6f8fe67..ad269f9 100644
--- a/main.py
+++ b/main.py
@@ -13,6 +13,7 @@ from ui.core.logger import setup_logger
from ui.views.config_page import create_config_page
from ui.views.home_page import create_home_page
from ui.views.data_page import create_data_page
+from ui.views.signature_page import create_signature_page
from utils.font_utils import install_fonts_from_directory
sys.stdout.reconfigure(encoding="utf-8")
@@ -95,6 +96,11 @@ def data_page():
create_data_page()
+@ui.page("/signature")
+def signature_page(folder: str = ""):
+ create_signature_page(folder)
+
+
# 4. 启动时钩子
async def startup_check():
try:
diff --git a/ui/views/home_page.py b/ui/views/home_page.py
index ef2f7a1..8f8aac8 100644
--- a/ui/views/home_page.py
+++ b/ui/views/home_page.py
@@ -1,7 +1,7 @@
from nicegui import ui
from config.config import load_config
from ui.core.state import app_state
-from ui.core.task_runner import run_task
+from ui.core.task_runner import run_task, select_folder
# 导入业务函数
from utils.generate_utils import (
@@ -10,7 +10,6 @@ from utils.generate_utils import (
generate_convert_pdf,
generate_report,
generate_zodiac,
- generate_signature,
)
from utils.file_utils import open_folder
@@ -71,7 +70,18 @@ def create_home_page():
func_btn("📊 生成报告 (PPT)", generate_report)
func_btn("📑 格式转换 (PDF)", generate_convert_pdf)
func_btn("🐂 生肖转化 (生日)", generate_zodiac)
- func_btn("💴 园长一键签名", generate_signature)
+
+ # 签名按钮
+ async def on_signature_click():
+ selected_folder = await select_folder()
+ if selected_folder:
+ ui.navigate.to(f"/signature?folder={selected_folder}")
+ else:
+ ui.notify("未选择目录", type="warning")
+
+ ui.button("💴 园长签名", on_click=on_signature_click).props(
+ f"outline"
+ ).classes("w-full")
# === 下方双栏布局 ===
# 数据管理
diff --git a/ui/views/signature_page.py b/ui/views/signature_page.py
new file mode 100644
index 0000000..6d9c81e
--- /dev/null
+++ b/ui/views/signature_page.py
@@ -0,0 +1,213 @@
+from nicegui import ui
+import os
+from config.config import load_config
+from utils.file_utils import open_folder
+from loguru import logger
+import traceback
+from pptx import Presentation
+
+
+def create_signature_page(folder: str = ""):
+ ui.add_head_html('')
+
+ # 添加样式
+ ui.add_head_html(
+ """
+
+ """
+ )
+
+ with ui.header().classes("app-header items-center justify-between shadow-md"):
+ # 左侧:图标和标题
+ with ui.row().classes("items-center gap-2"):
+ ui.image("/assets/icon.ico").classes("w-8 h-8").props("fit=contain")
+ ui.label("尚城幼儿园成长报告助手").classes("text-xl font-bold")
+ # 右侧:署名 + 配置按钮
+ with ui.row().classes("items-center gap-4"):
+ ui.label("By 寒寒 | 这里的每一份评语都充满爱意").classes(
+ "text-xs opacity-90"
+ )
+ ui.button(icon="home", on_click=lambda: ui.navigate.to("/")).props(
+ "flat round color=white"
+ )
+
+ with ui.card().classes("w-full"):
+ ui.label("💴 园长签名").classes("section-title")
+ with ui.row().classes("w-full items-center justify-between"):
+ with ui.row().classes("flex-1 items-center"):
+ ui.label("📁 当前目录:").classes(
+ "text-sm text-white border bg-[#2e8b57] p-2 rounded"
+ )
+ ui.label(f"{folder}").classes("text-sm text-gray-600")
+ with ui.row().classes("items-center"):
+ ui.button(
+ "💴 一键签名",
+ on_click=lambda: sign_all_files(folder),
+ ).props().classes()
+ ui.button(
+ "📂 打开文件夹",
+ on_click=lambda: open_folder(folder),
+ ).props("outline").classes()
+ ui.button(
+ "🔄 刷新数据",
+ on_click=lambda: for_file_list(list_card, folder),
+ ).props("outline").classes()
+
+ list_card = ui.card().classes("w-full p-4 gap-4")
+ for_file_list(list_card, folder)
+
+
+# 遍历目录
+def for_file_list(list_card, folder):
+ # 清空旧内容
+ list_card.clear()
+ files = get_signature_files(folder)
+ with list_card:
+ ui.label(f"📄 找到 {len(files)} 个 PPT 文件").classes(
+ "text-sm text-gray-600 mb-4"
+ )
+
+ # 存储选中的文件
+ selected_files = []
+
+ def toggle_file(file_name):
+ if file_name in selected_files:
+ selected_files.remove(file_name)
+ else:
+ selected_files.append(file_name)
+
+ # 创建可滚动的文件列表
+ with ui.grid(columns=2).classes("file-list"):
+ for ppt_file in files:
+ with ui.row().classes(
+ "w-full justify-between items-center file-item p-3 rounded mb-2"
+ ):
+ with ui.row().classes("flex-1 items-center"):
+ ui.checkbox(
+ on_change=lambda e, f=ppt_file: toggle_file(f)
+ ).classes("mr-3")
+ ui.label(ppt_file).classes("flex-1 text-sm")
+ with ui.row().classes("items-center gap-2"):
+ # 打开文件按钮
+ ui.button(
+ "📂 打开",
+ on_click=lambda f=ppt_file: open_folder(
+ os.path.join(folder, f)
+ ),
+ ).props("outline").classes("text-xs")
+ # 签名按钮
+ ui.button(
+ "💴 签名",
+ on_click=lambda f=ppt_file: (
+ sign_file(folder, f),
+ ui.notify(f"签名完成: {f}", type="positive"),
+ ),
+ ).props("outline").classes("text-xs")
+
+
+def get_signature_files(folder: str) -> list:
+ """获取目录下所有 PPT 文件"""
+ if not os.path.exists(folder):
+ return []
+ files = []
+ for filename in os.listdir(folder):
+ if not filename.startswith(".") and filename.endswith(".pptx"):
+ files.append(filename)
+ return sorted(files)
+
+
+def sign_file(folder, file_name: str):
+ """
+ 生成园长签名(不依赖占位符,直接在指定位置添加)
+ :param folder: PPT 文件所在目录
+ :param file_name: PPT 文件名称
+ """
+ try:
+ # 1. 加载配置文件
+ config = load_config("config.toml")
+ except Exception as e:
+ logger.error(f"配置文件获取失败: {str(e)}")
+ # 打印详细报错位置,方便调试
+ logger.error(traceback.format_exc())
+ try:
+ # 2. 等待签名文件路径
+ file_path = os.path.join(folder, file_name)
+ logger.info(f"开始生成签名,PPT文件:{file_name}")
+ # 加载签名图片路径
+ img_path = config.get("signature_image")
+ if not img_path or not os.path.exists(img_path):
+ logger.error(f"签名图片不存在: {img_path}")
+ logger.warning(f"⚠️ 警告: 缺少签名照片('signature')")
+ return
+ logger.info(f"签名图片存在: {img_path}")
+
+ # 从配置文件获取签名位置信息,如果没有则使用默认值
+ signature_left = config.get("signature_left", 2987040) # 左位置
+ signature_top = config.get("signature_top", 8273415) # 上位置
+ signature_width = config.get("signature_width", 1800000) # 宽度
+ signature_height = config.get("signature_height", 720000) # 高度
+ # 导入必要的模块
+ from utils.image_utils import get_corrected_image_stream
+
+ # 打开 PPT 对象
+ prs = Presentation(file_path)
+
+ # 获取第二张幻灯片 (索引为1)
+ slide = prs.slides[1]
+
+ # 获取修正后的图片流
+ img_stream = get_corrected_image_stream(img_path)
+
+ # 直接在指定位置添加签名图片
+ slide.shapes.add_picture(
+ img_stream,
+ signature_left,
+ signature_top,
+ signature_width,
+ signature_height,
+ )
+ # 保存修改后的 PPT
+ prs.save(file_path)
+ logger.info(f"签名完成,PPT文件:{file_name}")
+ except Exception as e:
+ logger.error(f"generate_signature 发生未知错误: {e}")
+ # 打印详细报错位置,方便调试
+ logger.error(traceback.format_exc())
+ return str(e)
+
+
+def sign_selected_files(folder: str, files: list):
+ """为选中的文件进行签名"""
+ total_files = len(files)
+ logger.info(f"开始生成签名,共 {total_files} 个文件")
+ for i, file_name in enumerate(files):
+ sign_file(folder, file_name)
+ logger.info(f"已为 {total_files} 个文件签名")
+ ui.notify(f"签名完成: {total_files} 个文件", type="positive")
+
+
+def sign_all_files(folder: str):
+ """为目录下所有文件进行签名"""
+ files = get_signature_files(folder)
+ if files:
+ sign_selected_files(folder, files)
+ total_files = len(files)
+ ui.notify(f"签名完成: {total_files} 个文件", type="positive")