Compare commits
2 Commits
1c2d5db393
...
de71594812
| Author | SHA1 | Date | |
|---|---|---|---|
| de71594812 | |||
| f3d16ec1f9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ data/images/*
|
||||
data/*.xlsx
|
||||
|
||||
.idea/
|
||||
.trae/
|
||||
7
.idea/workspace.xml
generated
7
.idea/workspace.xml
generated
@@ -4,7 +4,9 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="41690157-d51b-4dae-98de-6b96990d681a" name="更改" comment="fix:优化一些命名规范" />
|
||||
<list default="true" id="41690157-d51b-4dae-98de-6b96990d681a" name="更改" comment="fix:优化一些命名规范">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
@@ -95,6 +97,9 @@
|
||||
<workItem from="1766256207534" duration="960000" />
|
||||
<workItem from="1766287241685" duration="2135000" />
|
||||
<workItem from="1766329711762" duration="741000" />
|
||||
<workItem from="1768312728552" duration="228000" />
|
||||
<workItem from="1768312972093" duration="486000" />
|
||||
<workItem from="1768314152581" duration="7000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="fix:修复一些BUG">
|
||||
<option name="closed" value="true" />
|
||||
|
||||
@@ -4,7 +4,7 @@ output_folder = "output"
|
||||
excel_file = "names.xlsx"
|
||||
image_folder = "images"
|
||||
fonts_dir = "fonts"
|
||||
signature_image = "C:\\Users\\Administrator\\Desktop\\文档资料\\code\\growth_report\\data\\"
|
||||
signature_image = "d:\\working\\tools\\growth_report\\data\\signature.png"
|
||||
|
||||
[class_info]
|
||||
class_name = "K4D"
|
||||
|
||||
@@ -78,7 +78,7 @@ def load_config(config_filename="config.toml"):
|
||||
base_dir, paths.get("output_folder", "output")
|
||||
),
|
||||
"signature_image": get_resource_path(
|
||||
os.path.join("data", paths.get("signature_image", ""))
|
||||
os.path.join("data", paths.get("signature_image", "signature.png"))
|
||||
),
|
||||
"class_name": class_info.get("class_name", "未命名班级"),
|
||||
"teachers": class_info.get("teachers", []),
|
||||
@@ -118,7 +118,9 @@ def save_config(config_data, config_filename="config.toml"):
|
||||
"image_folder": os.path.basename(config_data.get("image_folder", "")),
|
||||
"fonts_dir": os.path.basename(config_data.get("fonts_dir", "fonts")),
|
||||
"signature_image": get_resource_path(
|
||||
os.path.join("data", config_data.get("signature_image", ""))
|
||||
os.path.join(
|
||||
"data", config_data.get("signature_image", "signature.png")
|
||||
)
|
||||
),
|
||||
},
|
||||
"class_info": {
|
||||
|
||||
@@ -168,6 +168,7 @@ def create_config_page():
|
||||
"output_folder": output_folder.value,
|
||||
"class_name": class_name.value,
|
||||
"age_group": age_group.value,
|
||||
"signature_image": conf_data.get("signature_image", ""),
|
||||
"teachers": [
|
||||
t.strip() for t in teachers_text.value.split("\n") if t.strip()
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ from ui.core.task_runner import run_task
|
||||
from utils.generate_utils import (
|
||||
generate_template,
|
||||
generate_comment_all,
|
||||
batch_convert_folder,
|
||||
generate_convert_pdf,
|
||||
generate_report,
|
||||
generate_zodiac,
|
||||
generate_signature,
|
||||
@@ -66,14 +66,10 @@ def create_home_page():
|
||||
f"outline"
|
||||
).classes("w-full")
|
||||
|
||||
# 特殊处理带参数的
|
||||
async def run_convert():
|
||||
await run_task(batch_convert_folder, config.get("output_folder"))
|
||||
|
||||
func_btn("📁 生成图片路径", generate_template)
|
||||
func_btn("🤖 生成评语 (AI)", generate_comment_all)
|
||||
func_btn("📊 生成报告 (PPT)", generate_report)
|
||||
func_btn("📑 格式转换 (PDF)", run_convert)
|
||||
func_btn("📑 格式转换 (PDF)", generate_convert_pdf)
|
||||
func_btn("🐂 生肖转化 (生日)", generate_zodiac)
|
||||
func_btn("💴 园长一键签名", generate_signature)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from utils.growt_utils import (
|
||||
replace_four_page,
|
||||
replace_five_page,
|
||||
)
|
||||
from utils.pptx_utils import replace_picture
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 1. 生成模板(根据names.xlsx文件生成名字图片文件夹)
|
||||
@@ -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:
|
||||
@@ -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,7 +369,7 @@ 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文件夹
|
||||
@@ -360,7 +386,7 @@ def batch_convert_folder(folder_path, stop_event: threading.Event = None, progre
|
||||
# 子线程初始化 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:
|
||||
# 打开 -> 另存为 -> 关闭
|
||||
@@ -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,12 +549,13 @@ 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. 加载配置文件
|
||||
try:
|
||||
@@ -551,22 +580,48 @@ def generate_signature(progress_callback=None) -> str:
|
||||
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)
|
||||
# 获取第二张幻灯片 (索引为1)
|
||||
slide = prs.slides[1]
|
||||
|
||||
# --- 关键修改点 3: 保存修改后的 PPT ---
|
||||
# 获取修正后的图片流
|
||||
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