267 lines
8.9 KiB
Python
267 lines
8.9 KiB
Python
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('<link href="/assets/style.css" rel="stylesheet" />')
|
||
|
||
# 添加样式
|
||
ui.add_head_html(
|
||
"""
|
||
<style>
|
||
.file-list { max-height: 450px; overflow-y: auto; }
|
||
.file-list::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
.file-list::-webkit-scrollbar-track {
|
||
background: #f1f1f1;
|
||
border-radius: 3px;
|
||
}
|
||
.file-list::-webkit-scrollbar-thumb {
|
||
background: #c1c1c1;
|
||
border-radius: 3px;
|
||
}
|
||
.file-list::-webkit-scrollbar-thumb:hover {
|
||
background: #a1a1a1;
|
||
}
|
||
.file-item { transition: background-color 0.2s ease; }
|
||
.file-item:hover { background-color: #f8f9fa; }
|
||
</style>
|
||
"""
|
||
)
|
||
|
||
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")
|