fix(bug):修复转换PDF功能BUG,优化园长签名生成逻辑,让更多的文档支持园长签名功能
This commit is contained in:
@@ -22,13 +22,13 @@ from utils.growt_utils import (
|
||||
replace_four_page,
|
||||
replace_five_page,
|
||||
)
|
||||
from utils.pptx_utils import replace_picture
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 1. 生成模板(根据names.xlsx文件生成名字图片文件夹)
|
||||
# ==========================================
|
||||
def generate_template(stop_event: threading.Event = None, progress_callback=None):
|
||||
""""
|
||||
""" "
|
||||
根据学生姓名生成相对应的以学生姓名的存放照片的文件夹
|
||||
:params stop_event 任务是否停止事件(监听UI的事件监听)
|
||||
:params progress_callback 进度回调函数
|
||||
@@ -36,7 +36,7 @@ def generate_template(stop_event: threading.Event = None, progress_callback=None
|
||||
# 1. 加载配置文件
|
||||
try:
|
||||
config = load_config("config.toml")
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件获取失败: {str(e)}")
|
||||
# 打印详细报错位置,方便调试
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -87,10 +87,10 @@ def generate_comment_all(stop_event: threading.Event = None, progress_callback=N
|
||||
:params stop_event 任务是否停止事件(监听UI的事件监听)
|
||||
:params progress_callback 进度回调函数
|
||||
"""
|
||||
# 1. 加载配置文件
|
||||
# 1. 加载配置文件
|
||||
try:
|
||||
config = load_config("config.toml")
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件获取失败: {str(e)}")
|
||||
# 打印详细报错位置,方便调试
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -139,7 +139,9 @@ def generate_comment_all(stop_event: threading.Event = None, progress_callback=N
|
||||
continue
|
||||
# 添加进度条
|
||||
if progress_callback:
|
||||
progress_callback(i + 1, total_count, f"[{i + 1}/{total_count}] 正在生成评价: {name}")
|
||||
progress_callback(
|
||||
i + 1, total_count, f"[{i + 1}/{total_count}] 正在生成评价: {name}"
|
||||
)
|
||||
logger.info(f"[{i + 1}/{total_count}] 正在生成评价: {name}")
|
||||
|
||||
try:
|
||||
@@ -180,10 +182,10 @@ def generate_report(stop_event: threading.Event = None, progress_callback=None):
|
||||
根据学生姓名生成成长报告
|
||||
:params stop_event 任务是否停止事件(监听UI的事件监听)
|
||||
:params progress_callback 进度回调函数
|
||||
""" # 1. 加载配置文件
|
||||
""" # 1. 加载配置文件
|
||||
try:
|
||||
config = load_config("config.toml")
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件获取失败: {str(e)}")
|
||||
# 打印详细报错位置,方便调试
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -237,7 +239,11 @@ def generate_report(stop_event: threading.Event = None, progress_callback=None):
|
||||
) = row_data
|
||||
# 更新进度条
|
||||
if progress_callback:
|
||||
progress_callback(i + 1, total_count, f"[{i + 1}/{len(datas)}] 正在生成: 【{name}】 成长报告")
|
||||
progress_callback(
|
||||
i + 1,
|
||||
total_count,
|
||||
f"[{i + 1}/{len(datas)}] 正在生成: 【{name}】 成长报告",
|
||||
)
|
||||
logger.info(f"[{i + 1}/{len(datas)}] 正在生成: {name}")
|
||||
|
||||
# 每次循环重新加载模版
|
||||
@@ -271,11 +277,31 @@ def generate_report(stop_event: threading.Event = None, progress_callback=None):
|
||||
"birthday": (
|
||||
birthday.strftime("%Y-%m-%d") if pd.notna(birthday) else " "
|
||||
),
|
||||
"zodiac": str(zodiac).strip() if str(zodiac).strip() or not str(zodiac).strip().lower() else " ",
|
||||
"friend": str(friend).strip() if str(friend).strip() or not str(friend).strip().lower() else " ",
|
||||
"hobby": str(hobby).strip() if str(hobby).strip() or not str(hobby).strip().lower() else " ",
|
||||
"game": str(game).strip() if str(game).strip() or not str(game).strip().lower() else " ",
|
||||
"food": str(food).strip() if str(food).strip() or not str(food).strip().lower() else " ",
|
||||
"zodiac": (
|
||||
str(zodiac).strip()
|
||||
if str(zodiac).strip() or not str(zodiac).strip().lower()
|
||||
else " "
|
||||
),
|
||||
"friend": (
|
||||
str(friend).strip()
|
||||
if str(friend).strip() or not str(friend).strip().lower()
|
||||
else " "
|
||||
),
|
||||
"hobby": (
|
||||
str(hobby).strip()
|
||||
if str(hobby).strip() or not str(hobby).strip().lower()
|
||||
else " "
|
||||
),
|
||||
"game": (
|
||||
str(game).strip()
|
||||
if str(game).strip() or not str(game).strip().lower()
|
||||
else " "
|
||||
),
|
||||
"food": (
|
||||
str(food).strip()
|
||||
if str(food).strip() or not str(food).strip().lower()
|
||||
else " "
|
||||
),
|
||||
}
|
||||
# 获取学生个人照片路径
|
||||
me_image_path = find_image_path(student_image_folder, "me")
|
||||
@@ -343,24 +369,24 @@ def generate_report(stop_event: threading.Event = None, progress_callback=None):
|
||||
# ==========================================
|
||||
# 4. 转换格式(根据names.xlsx文件生成PPT转PDF)
|
||||
# ==========================================
|
||||
def batch_convert_folder(folder_path, stop_event: threading.Event = None, progress_callback=None):
|
||||
def generate_convert_pdf(stop_event: threading.Event = None, progress_callback=None):
|
||||
"""
|
||||
批量转换文件夹下的所有 PPT
|
||||
:params folder_path 需要转换的PPT文件夹
|
||||
:params stop_event 任务是否停止事件(监听UI的事件监听)
|
||||
:params progress_callback 进度回调函数
|
||||
"""
|
||||
# 1. 加载配置文件
|
||||
# 1. 加载配置文件
|
||||
try:
|
||||
config = load_config("config.toml")
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件获取失败: {str(e)}")
|
||||
# 打印详细报错位置,方便调试
|
||||
logger.error(traceback.format_exc())
|
||||
# 子线程初始化 COM 组件
|
||||
pythoncom.CoInitialize()
|
||||
try:
|
||||
folder_path = os.path.abspath(folder_path)
|
||||
folder_path = os.path.abspath(config.get("output_folder"))
|
||||
if not os.path.exists(folder_path):
|
||||
logger.error(f"文件夹不存在: {folder_path}")
|
||||
return
|
||||
@@ -400,7 +426,9 @@ def batch_convert_folder(folder_path, stop_event: threading.Event = None, progre
|
||||
logger.info(f"[跳过] 已存在: {filename}")
|
||||
continue
|
||||
|
||||
logger.info(f"[{files.index(filename)}/{total_count}]正在转换: {filename} ...")
|
||||
logger.info(
|
||||
f"[{files.index(filename)}/{total_count}]正在转换: {filename} ..."
|
||||
)
|
||||
|
||||
try:
|
||||
# 打开 -> 另存为 -> 关闭
|
||||
@@ -443,7 +471,7 @@ def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
|
||||
# 1. 加载配置文件
|
||||
try:
|
||||
config = load_config("config.toml")
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件获取失败: {str(e)}")
|
||||
# 打印详细报错位置,方便调试
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -467,7 +495,7 @@ def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
|
||||
logger.info(f"开始生成学生属相,共 {total_count} 位学生...")
|
||||
|
||||
# 3. 预处理:将“生日”列转换为 datetime 格式
|
||||
df['temp_date'] = pd.to_datetime(df[date_column], errors="coerce")
|
||||
df["temp_date"] = pd.to_datetime(df[date_column], errors="coerce")
|
||||
|
||||
# 4. 遍历 DataFrame 并计算/更新数据
|
||||
for i, row in df.iterrows():
|
||||
@@ -481,7 +509,7 @@ def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
|
||||
progress_callback(i + 1, total_count, "生成属相")
|
||||
|
||||
name = row.get("姓名", f"学生_{i + 1}")
|
||||
date = row['temp_date']
|
||||
date = row["temp_date"]
|
||||
|
||||
logger.info(f"[{i + 1}/{total_count}] 正在处理学生:{name}...")
|
||||
|
||||
@@ -502,7 +530,7 @@ def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
|
||||
logger.info(f" -> 属相计算成功:{name} ,属相: {zodiac}")
|
||||
|
||||
# 6. 清理和保存结果
|
||||
df = df.drop(columns=['temp_date'])
|
||||
df = df.drop(columns=["temp_date"])
|
||||
|
||||
save_path = excel_path
|
||||
try:
|
||||
@@ -521,52 +549,79 @@ def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
|
||||
logger.error(f"程序运行出错: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 6. 一键生成园长签名(根据输出文件夹生成签名)
|
||||
# ==========================================
|
||||
def generate_signature(progress_callback=None) -> str:
|
||||
"""
|
||||
生成园长签名
|
||||
生成园长签名(不依赖占位符,直接在指定位置添加)
|
||||
"""
|
||||
# 1. 加载配置文件
|
||||
# 1. 加载配置文件
|
||||
try:
|
||||
config = load_config("config.toml")
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件获取失败: {str(e)}")
|
||||
# 打印详细报错位置,方便调试
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
# 获取所有的PPT (此时返回的是文件名或路径的列表)
|
||||
pptx_files = get_output_pptx_files(config["output_folder"])
|
||||
|
||||
|
||||
if not pptx_files:
|
||||
logger.warning("没有找到 PPT 文件")
|
||||
return "未找到文件"
|
||||
|
||||
logger.info(f"开始生成签名,共 {len(pptx_files)} 个 PPT 文件...")
|
||||
|
||||
img_path = config.get("signature_image") # 签名图片路径
|
||||
|
||||
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
|
||||
|
||||
for i, filename in enumerate(pptx_files):
|
||||
# 获取完整绝对路径
|
||||
pptx_path = os.path.join(config["output_folder"], filename)
|
||||
|
||||
# --- 关键修改点 1: 打开 PPT 对象 ---
|
||||
|
||||
# 打开 PPT 对象
|
||||
prs = Presentation(pptx_path)
|
||||
|
||||
# --- 关键修改点 2: 传递 prs 对象而不是路径字符串 ---
|
||||
replace_picture(prs, 1, "signature", img_path)
|
||||
|
||||
# --- 关键修改点 3: 保存修改后的 PPT ---
|
||||
|
||||
# 获取第二张幻灯片 (索引为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,
|
||||
)
|
||||
logger.info(f"在幻灯片 1 上添加签名图片")
|
||||
|
||||
# 保存修改后的 PPT
|
||||
prs.save(pptx_path)
|
||||
|
||||
|
||||
# 更新进度条 (如果有 callback)
|
||||
if progress_callback:
|
||||
progress_callback(i + 1, len(pptx_files),f"[{i + 1}/{len(pptx_files)}] 生成签名完成: {filename}")
|
||||
progress_callback(
|
||||
i + 1,
|
||||
len(pptx_files),
|
||||
f"[{i + 1}/{len(pptx_files)}] 生成签名完成: {filename}",
|
||||
)
|
||||
logger.success(f"[{i + 1}/{len(pptx_files)}] 生成签名完成: {filename}")
|
||||
if progress_callback:
|
||||
progress_callback(len(pptx_files), len(pptx_files), "签名生成完成")
|
||||
|
||||
@@ -100,14 +100,18 @@ def replace_text_in_slide(prs, slide_index, placeholder, text):
|
||||
try:
|
||||
run.font.space = original_font["character_space"]
|
||||
except:
|
||||
logger.error(f"错误: 无法设置字体间距 {original_font['character_space']}")
|
||||
logger.error(
|
||||
f"错误: 无法设置字体间距 {original_font['character_space']}"
|
||||
)
|
||||
|
||||
if original_font["color"]:
|
||||
try:
|
||||
if hasattr(original_font["color"], "rgb"):
|
||||
run.font.color.rgb = original_font["color"].rgb
|
||||
except:
|
||||
logger.error(f"错误: 无法设置字体颜色 {original_font['color']}")
|
||||
logger.error(
|
||||
f"错误: 无法设置字体颜色 {original_font['color']}"
|
||||
)
|
||||
|
||||
|
||||
def replace_picture(prs, slide_index, placeholder, img_path):
|
||||
@@ -138,6 +142,10 @@ def replace_picture(prs, slide_index, placeholder, img_path):
|
||||
break
|
||||
|
||||
if target_shape:
|
||||
logger.debug(f"找到占位符 {placeholder},索引 {target_index}")
|
||||
logger.debug(
|
||||
f"占位符位置: 左 {target_shape.left}, 上 {target_shape.top}, 宽 {target_shape.width}, 高 {target_shape.height}"
|
||||
)
|
||||
# 获取原位置信息
|
||||
left, top, width, height = (
|
||||
target_shape.left,
|
||||
|
||||
Reference in New Issue
Block a user