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")