From 6a0c826a064b0c06607029609fa843606c86b4f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=AF=92=E5=AF=92?= <2596194220@qq.com>
Date: Thu, 29 Jan 2026 23:10:06 +0800
Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E4=BC=98=E5=8C=96PDF=E8=BD=AC?=
=?UTF-8?q?=E6=8D=A2=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 81 ++++++++---
main.py | 9 +-
ui/views/convert_pdf_page.py | 266 +++++++++++++++++++++++++++++++++++
ui/views/home_page.py | 18 ++-
4 files changed, 346 insertions(+), 28 deletions(-)
create mode 100644 ui/views/convert_pdf_page.py
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 1aa8f25..53cf388 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,13 +5,23 @@
+
+
+
+
+
+
@@ -24,26 +34,51 @@
- {
- "keyToString": {
- "ModuleVcsDetector.initialDetectionPerformed": "true",
- "Python.main_nicegui.executor": "Run",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
- "RunOnceActivity.git.unshallow": "true",
- "RunOnceActivity.typescript.service.memoryLimit.init": "true",
- "SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "git-widget-placeholder": "master",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "preferences.lookFeel",
- "vue.rearranger.settings.migration": "true"
+
-
+}]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -70,6 +105,7 @@
+
@@ -77,8 +113,8 @@
-
-
+
+
@@ -100,6 +136,8 @@
+
+
@@ -159,6 +197,7 @@
+
\ No newline at end of file
diff --git a/main.py b/main.py
index ad269f9..f721ff9 100644
--- a/main.py
+++ b/main.py
@@ -7,12 +7,12 @@ from nicegui import ui, app, run, native
from screeninfo import get_monitors
from config.config import load_config
-
# 导入我们的模块
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.convert_pdf_page import create_convert_pdf_page
from ui.views.data_page import create_data_page
+from ui.views.home_page import create_home_page
from ui.views.signature_page import create_signature_page
from utils.font_utils import install_fonts_from_directory
@@ -101,6 +101,10 @@ def signature_page(folder: str = ""):
create_signature_page(folder)
+@ui.page("/convert_pdf")
+def convert_pdf_page(folder: str = ""):
+ create_convert_pdf_page(folder)
+
# 4. 启动时钩子
async def startup_check():
try:
@@ -122,5 +126,4 @@ if __name__ in {"__main__", "__mp_main__"}:
native=True,
window_size=calculated_size,
port=native.find_open_port(), # 自动寻找端口
- reload=True,
)
diff --git a/ui/views/convert_pdf_page.py b/ui/views/convert_pdf_page.py
new file mode 100644
index 0000000..ee4fb24
--- /dev/null
+++ b/ui/views/convert_pdf_page.py
@@ -0,0 +1,266 @@
+import os
+
+import comtypes.client
+import pythoncom
+from loguru import logger
+from nicegui import ui
+
+from utils.file_utils import open_folder
+
+progress_bar = None
+progress_label = None
+powerpoint = None
+
+
+def onload_page(folder: str = ""):
+ global powerpoint
+ pdf_path = os.path.join(folder, "PDF")
+ if not os.path.exists(pdf_path):
+ os.makedirs(pdf_path)
+ if not powerpoint:
+ try:
+ pythoncom.CoInitialize()
+ powerpoint = comtypes.client.CreateObject("PowerPoint.Application")
+ logger.success("PowerPoint 应用启动成功")
+ except Exception as e:
+ logger.error(f"PowerPoint 应用启动失败: {str(e)}")
+
+
+def update_progress(current: int, total: int, message: str):
+ global progress_bar, progress_label
+ if total <= 0:
+ pct = 0
+ text = f"{message}: 准备中..."
+ else:
+ pct = current / total
+ text = f"{message}: {current}/{total} ({int(pct * 100)}%)"
+
+ # 更新 UI
+ if progress_bar:
+ progress_bar.set_value(pct)
+ if progress_label:
+ progress_label.set_text(text)
+
+
+def cleanup_powerpoint():
+ """清理 PowerPoint COM 组件资源"""
+ global powerpoint
+ if powerpoint:
+ try:
+ powerpoint.Quit()
+ logger.success("PowerPoint 应用已关闭")
+ except Exception as e:
+ logger.error(f"关闭 PowerPoint 应用失败: {str(e)}")
+ finally:
+ powerpoint = None
+
+
+def create_convert_pdf_page(folder: str = ""):
+ onload_page(folder)
+ 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"):
+ with ui.row().classes("w-full justify-between"):
+ ui.label("📑 格式转换").classes("section-title")
+ with ui.row().classes("flex-1 justify-end"):
+ ui.button(
+ "📑 格式转换",
+ on_click=lambda: convert_all_pdf_files(folder),
+ ).props().classes()
+ ui.button(
+ "📂 打开文件夹",
+ on_click=lambda: open_folder(folder),
+ ).props("outline").classes()
+ ui.button(
+ "🔄 刷新数据",
+ on_click=lambda: convert_files_list(list_card, folder),
+ ).props("outline").classes()
+ # 进度条
+ with ui.row().classes("w-full"):
+ global progress_label, progress_bar
+ progress_bar = ui.linear_progress(value=0, show_value=False).classes(
+ "h-4 rounded"
+ )
+ progress_bar.props("color=positive")
+ progress_label = ui.label("⛳ 任务进度: 待命").classes(
+ "font-bold text-gray-700 mb-1"
+ )
+
+ list_card = ui.card().classes("w-full p-4 gap-4")
+ convert_files_list(list_card, folder)
+
+
+# 遍历目录
+def convert_files_list(list_card, folder):
+ # 清空旧内容
+ list_card.clear()
+ files = get_convert_files(folder)
+ with list_card:
+ ui.label(f"📄 找到 {len(files)} 个 PPT 文件").classes(
+ "text-sm text-gray-600 mb-4"
+ )
+ # 创建可滚动的文件列表
+ 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"
+ ):
+ 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: (convert_pdf_file(folder, f),),
+ ).props("outline").classes("text-xs")
+
+
+def get_convert_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 convert_file(folder, file_name: str):
+ """
+ 转换单个PPT文件为PDF格式
+ :param folder: PPT 文件所在目录
+ :param file_name: PPT 文件名称
+ """
+ global progress_bar, progress_label, powerpoint
+
+ if not powerpoint:
+ logger.error("PowerPoint 应用未初始化,请重新加载页面")
+ ui.notify("PowerPoint 应用未初始化", type="negative")
+ return
+
+ try:
+ ppt_path = os.path.join(folder, file_name)
+ pdf_name = os.path.splitext(file_name)[0] + ".pdf"
+ pdf_path = os.path.join(folder, "PDF", pdf_name)
+
+ # 如果 PDF 已存在,可以选择跳过
+ if os.path.exists(pdf_path):
+ logger.info(f"[跳过] 已存在: {pdf_name}")
+ return True
+
+ # 打开 -> 另存为 -> 关闭
+ deck = powerpoint.Presentations.Open(ppt_path)
+ deck.SaveAs(pdf_path, 32)
+ deck.Close()
+ logger.success(f"文件转换完成: {file_name}")
+ return True
+ except Exception as e:
+ error_msg = f"转换失败: {str(e)}"
+ logger.error(error_msg)
+ return False
+
+
+def convert_pdf_file(folder, file_name: str):
+ """
+ 转换单个PPT文件为PDF格式(带UI更新)
+ :param folder: PPT 文件所在目录
+ :param file_name: PPT 文件名称
+ """
+ update_progress(0, 1, f"⛳ 任务进度: 开始转换 {file_name}")
+ result = convert_file(folder, file_name)
+
+ if result is True:
+ update_progress(1, 1, f"✅ 转换完成: {file_name}")
+ ui.notify(f"转换完成: {file_name}", type="positive")
+ elif result is None:
+ update_progress(1, 1, f"⏭️ 已存在: {file_name}")
+ ui.notify(f"已存在: {file_name}", type="info")
+ else:
+ update_progress(1, 1, f"❌ 转换失败: {file_name}")
+ ui.notify(f"转换失败: {file_name}", type="negative")
+
+
+def convert_all_pdf_files(folder: str):
+ """批量转换目录下所有PPT文件为PDF格式"""
+ global progress_bar, progress_label
+
+ pdf_files = get_convert_files(folder)
+ pdf_total = len(pdf_files)
+
+ if pdf_total == 0:
+ ui.notify("没有找到需要转换的PPT文件", type="warning")
+ return
+
+ success_count = 0
+ skip_count = 0
+ fail_count = 0
+
+ try:
+ for index, file in enumerate(pdf_files):
+ update_progress(index, pdf_total, f"正在转换: {file}")
+ result = convert_file(folder, file)
+
+ if result is True:
+ success_count += 1
+ elif result is None:
+ skip_count += 1
+ else:
+ fail_count += 1
+
+ update_progress(pdf_total, pdf_total, f"✅ 批量转换完成")
+
+ summary_msg = f"转换完成: 成功 {success_count} 个, 跳过 {skip_count} 个, 失败 {fail_count} 个"
+ logger.success(summary_msg)
+ ui.notify(summary_msg, type="positive")
+
+ except Exception as e:
+ error_msg = f"批量转换失败: {str(e)}"
+ update_progress(pdf_total, pdf_total, f"❌ 批量转换失败")
+ logger.error(error_msg)
+ ui.notify(error_msg, type="negative")
diff --git a/ui/views/home_page.py b/ui/views/home_page.py
index 8f8aac8..595e1f6 100644
--- a/ui/views/home_page.py
+++ b/ui/views/home_page.py
@@ -1,17 +1,16 @@
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, select_folder
-
+from utils.file_utils import open_folder
# 导入业务函数
from utils.generate_utils import (
generate_template,
generate_comment_all,
- generate_convert_pdf,
generate_report,
generate_zodiac,
)
-from utils.file_utils import open_folder
config = load_config("config.toml")
@@ -68,9 +67,20 @@ def create_home_page():
func_btn("📁 生成图片路径", generate_template)
func_btn("🤖 生成评语 (AI)", generate_comment_all)
func_btn("📊 生成报告 (PPT)", generate_report)
- func_btn("📑 格式转换 (PDF)", generate_convert_pdf)
func_btn("🐂 生肖转化 (生日)", generate_zodiac)
+ # 格式转换按钮
+ async def on_convert_pdf_click():
+ selected_folder = await select_folder()
+ if selected_folder:
+ ui.navigate.to(f"/convert_pdf?folder={selected_folder}")
+ else:
+ ui.notify("未选择目录", type="warning")
+
+ ui.button("📑 格式转换 (PDF)", on_click=on_convert_pdf_click).props(
+ f"outline"
+ ).classes("w-full")
+
# 签名按钮
async def on_signature_click():
selected_folder = await select_folder()