fix:修复一些BUG

This commit is contained in:
2025-12-13 19:44:27 +08:00
parent 9d347f9bc9
commit 93d1e8687a
18 changed files with 584 additions and 638 deletions

View File

@@ -1,4 +1,5 @@
import os
import threading
import time
import pythoncom
@@ -12,7 +13,6 @@ import comtypes.client
from config.config import load_config
from utils.agent_utils import generate_comment
from utils.file_utils import check_file_exists
from utils.font_utils import install_fonts_from_directory
from utils.image_utils import find_image_path
from utils.zodiac_utils import calculate_zodiac
from utils.growt_utils import (
@@ -23,7 +23,6 @@ from utils.growt_utils import (
replace_five_page,
)
# 如果你之前没有全局定义 console这里定义一个
console = Console()
@@ -36,26 +35,33 @@ config = load_config("config.toml")
# ==========================================
# 1. 生成模板(根据names.xlsx文件生成名字图片文件夹)
# ==========================================
def generate_template():
def generate_template(stop_event: threading.Event = None, progress_callback=None):
""""
根据学生姓名生成相对应的以学生姓名的存放照片的文件夹
:params stop_event 任务是否停止事件监听UI的事件监听
:params progress_callback 进度回调函数
"""
try:
# 2. 读取数据
# 1. 读取数据
df = pd.read_excel(config["excel_file"], sheet_name="Sheet1")
# --- 修改点开始 ---
# 直接读取 "姓名" 这一列,不使用列表包裹列名,这样得到的是一维数据
# 2. 获取姓名数据
datas = df["姓名"].values.tolist()
# --- 修改点结束 ---
logger.info(f"开始生成学生模版文件,共 {len(datas)} 位学生...")
total_count = len(datas)
logger.info(f"开始生成学生模版文件,共 {total_count} 位学生...")
# 3. 循环处理
# 此时 name 就是字符串 '张三',而不是列表 ['张三']
for i, name in enumerate(datas):
logger.info(f"[{i + 1}/{len(datas)}] 正在生成: {name}")
# 判断是否有停止事件
if stop_event and stop_event.is_set():
logger.warning("任务正在停止中,正在中断中.....")
return # 停止任务
# 添加进度条
if progress_callback:
progress_callback(i + 1, total_count, "生成学生图片文件夹")
logger.info(f"[{i + 1}/{len(datas)}] 正在生成: {name}")
# 确保 name 是字符串且去除了空格 (增加健壮性)
name = str(name).strip()
# 生成学生图片的文件夹
student_folder = os.path.join(config["image_folder"], name)
if os.path.exists(student_folder):
@@ -63,7 +69,10 @@ def generate_template():
else:
logger.info(f"正在生成学生图片文件夹 {student_folder}")
os.makedirs(student_folder, exist_ok=True)
# 更新进度条为100%
if progress_callback:
progress_callback(total_count, total_count, "生成学生图片文件夹")
logger.success("✅ 所有学生模版文件已生成完毕")
except Exception as e:
logger.error(f"程序运行出错: {str(e)}")
# 打印详细报错位置,方便调试
@@ -73,7 +82,12 @@ def generate_template():
# ==========================================
# 2. 生成评语(根据names.xlsx文件生成评价)
# ==========================================
def generate_comment_all():
def generate_comment_all(stop_event: threading.Event = None, progress_callback=None):
"""
根据学生姓名生成评价
:params stop_event 任务是否停止事件监听UI的事件监听
:params progress_callback 进度回调函数
"""
try:
# 1. 读取数据
excel_path = config["excel_file"]
@@ -85,23 +99,27 @@ def generate_comment_all():
# 获取学生数据行数
total_count = len(df)
logger.info(f"开始生成学生评语,共 {total_count} 位学生...")
logger.info(f"开始生成学生评语,共 {len(df)} 位学生...")
# 强制将“评价”列转换为 object 类型
df["评价"] = df["评价"].astype("object")
# --- 遍历 DataFrame 的索引 (index) ---
# 这样我们可以通过索引 i 精准地把数据写回某一行
for i in df.index:
name = df.at[i, "姓名"] # 获取当前行的姓名
sex = df.at[i, "性别"]
if pd.isna(sex):
sex = ""
else:
sex = str(sex).strip()
# 健壮性处理
if stop_event and stop_event.is_set():
logger.warning("任务正在停止中,正在中断中.....")
return # 停止任务
# 添加进度条
if progress_callback:
progress_callback(i + 1, total_count, "生成学生评语")
# 获取学生姓名
name = df.at[i, "姓名"]
if pd.isna(name):
continue # 跳过空行
name = str(name).strip()
continue
else:
name = str(name).strip()
# 获取性别
sex = pd.isna(df.at[i, "性别"]) if "" else str(df.at[i, "性别"]).strip()
# 获取当前行的特征如果Excel里有“特征”这一列就读没有就用默认值
# 假设Excel里有一列叫 "表现特征",如果没有则用默认的 "有礼貌..."
@@ -124,11 +142,7 @@ def generate_comment_all():
generated_text = generate_comment(
name, config["age_group"], traits, sex
)
if generated_text:
# 赋值
df.at[i, "评价"] = str(generated_text).strip()
else:
df.at[i, "评价"] = "" # 防空处理
df.at[i, "评价"] = generated_text if str(generated_text).strip() else ""
logger.success(f"学生:{name},评语生成完毕")
# 可选:每生成 5 个就保存一次
@@ -139,11 +153,12 @@ def generate_comment_all():
time.sleep(1)
except Exception as e:
logger.error(f"学生:{name},生成评语出错: {str(e)}")
# --- 循环结束后最终保存文件 ---
# index=False 表示不把 pandas 的索引 (0,1,2...) 写到 Excel 第一列
df.to_excel(excel_path, index=False)
logger.success(f"所有评语已生成并写入文件:{excel_path}")
if progress_callback:
progress_callback(total_count, total_count, "生成学生评语")
except PermissionError:
logger.error(f"保存失败!请先关闭 Excel 文件:{config['excel_file']}")
@@ -155,26 +170,23 @@ def generate_comment_all():
# ==========================================
# 3. 生成成长报告(根据names.xlsx文件生成)
# ==========================================
def generate_report():
# 1. 资源准备
if install_fonts_from_directory(config["fonts_dir"]):
logger.info("等待系统识别新安装的字体...")
time.sleep(2)
os.makedirs(config["output_folder"], exist_ok=True)
# 检查模版文件是否存在
def generate_report(stop_event: threading.Event = None, progress_callback=None):
"""
根据学生姓名生成成长报告
:params stop_event 任务是否停止事件监听UI的事件监听
:params progress_callback 进度回调函数
"""
# 1. 检查模版文件是否存在
if not os.path.exists(config["source_file"]):
logger.info(f"错误: 找不到模版文件 {config["source_file"]}")
return
# 检查数据文件是否存在
# 2. 检查数据文件是否存在
if not os.path.exists(config["excel_file"]):
logger.info(f"错误: 找不到数据文件 {config['excel_file']}")
return
try:
# 2. 读取数据
# 1. 读取数据
df = pd.read_excel(config["excel_file"], sheet_name="Sheet1")
# 确保列名对应
# 2. 确保列名对应
columns = [
"姓名",
"英文名",
@@ -187,14 +199,21 @@ def generate_report():
"喜欢吃的食物",
"评价",
]
# 获取数据列表
datas = df[columns].values.tolist()
total_count = len(datas)
# 获取配置文件的教师签名
teacher_names_str = " ".join(config["teachers"])
logger.info(f"开始处理,共 {len(datas)} 位学生...")
logger.info(f"开始处理,共 {total_count} 位学生...")
# 3. 循环处理
for i, row_data in enumerate(datas):
if stop_event and stop_event.is_set():
logger.warning("任务正在停止中,正在中断中.....")
return
# 更新进度条
if progress_callback:
progress_callback(i + 1, total_count, "生成报告")
# 解包数据
(
name,
@@ -237,16 +256,16 @@ def generate_report():
# 构造学生信息字典
student_info_dict = {
"name": name,
"english_name": english_name if pd.notna(english_name) else "",
"english_name": english_name if pd.notna(english_name) else " ",
"sex": sex if pd.notna(sex) else "",
"birthday": (
birthday.strftime("%Y-%m-%d") if pd.notna(birthday) else ""
birthday.strftime("%Y-%m-%d") if pd.notna(birthday) else " "
),
"zodiac": zodiac if pd.notna(zodiac) else "",
"friend": friend if pd.notna(friend) else "",
"hobby": hobby if pd.notna(hobby) else "",
"game": game if pd.notna(game) else "",
"food": food if pd.notna(food) 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")
@@ -254,6 +273,7 @@ def generate_report():
if check_file_exists(me_image_path):
replace_three_page(prs, student_info_dict, me_image_path)
else:
replace_three_page(prs, student_info_dict)
logger.warning(f"⚠️ 警告: 学生图片文件不存在 {me_image_path}")
# --- 页面 4 ---
@@ -266,7 +286,7 @@ def generate_report():
replace_four_page(prs, class_image_path)
else:
logger.warning(f"⚠️ 警告: 班级图片文件不存在 {class_image_path}")
# --- 页面 5 ---
if os.path.exists(student_image_folder):
img1_path = find_image_path(student_image_folder, "1")
@@ -301,6 +321,8 @@ def generate_report():
f"保存失败: 文件 {new_filename} 可能已被打开,请关闭后重试。"
)
if progress_callback:
progress_callback(total_count, total_count, "生成报告")
logger.success("所有报告生成完毕!")
except Exception as e:
@@ -311,10 +333,12 @@ def generate_report():
# ==========================================
# 5. 转换格式(根据names.xlsx文件生成PPT转PDF)
# ==========================================
def batch_convert_folder(folder_path):
def batch_convert_folder(folder_path, stop_event: threading.Event = None, progress_callback=None):
"""
【推荐】批量转换文件夹下的所有 PPT (只启动一次 PowerPoint速度快)
已修复多线程 CoInitialize 报错,并适配 GUI 日志
批量转换文件夹下的所有 PPT
:params folder_path 需要转换的PPT文件夹
:params stop_event 任务是否停止事件监听UI的事件监听
:params progress_callback 进度回调函数
"""
# 子线程初始化 COM 组件
pythoncom.CoInitialize()
@@ -333,18 +357,24 @@ def batch_convert_folder(folder_path):
logger.warning("没有找到 PPT 文件")
return
logger.info(f"发现 {len(files)} 个文件,准备开始转换...")
total_count = len(files)
logger.info(f"发现 {total_count} 个文件,准备开始转换...")
powerpoint = None
try:
# 1. 启动应用 (只启动一次)
powerpoint = comtypes.client.CreateObject("PowerPoint.Application")
# 【建议】在后台线程运行时,有时设置为不可见更稳定,
# 但如果遇到转换卡死,可以尝试去掉下面这行的注释,让它显示出来
# 设置是否显示转化页面,但如果遇到转换卡死,可以尝试去掉下面这行的注释,让它显示出来
# powerpoint.Visible = 1
for filename in files:
if stop_event and stop_event.is_set():
logger.warning("任务正在停止中,正在中断中.....")
return
# 添加进度条
if progress_callback:
progress_callback(files.index(filename), total_count, "转换PDF")
ppt_path = os.path.join(folder_path, filename)
pdf_path = os.path.splitext(ppt_path)[0] + ".pdf"
@@ -353,7 +383,7 @@ def batch_convert_folder(folder_path):
logger.info(f"[跳过] 已存在: {filename}")
continue
logger.info(f"正在转换: {filename} ...")
logger.info(f"[{files.index(filename)}/{total_count}]正在转换: {filename} ...")
try:
# 打开 -> 另存为 -> 关闭
@@ -363,6 +393,9 @@ def batch_convert_folder(folder_path):
except Exception as e:
logger.error(f"文件 {filename} 转换出错: {e}")
# 添加进度条
if progress_callback:
progress_callback(total_count, total_count, "转换PDF")
except Exception as e:
logger.error(f"PowerPoint 进程启动出错: {e}")
finally:
@@ -384,11 +417,15 @@ def batch_convert_folder(folder_path):
# ==========================================
# 5. 生成属相(根据names.xlsx文件生成属相)
# ==========================================
def generate_zodiac():
def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
"""
生成学生属相,如果“生日”列为空,则跳过该学生。
:params stop_event 任务是否停止事件监听UI的事件监听
:params progress_callback 进度回调函数
"""
try:
# 1. 读取数据
excel_path = config["excel_file"]
# sheet_name 根据实际情况修改,如果不确定可以用 sheet_name=0 读取第一个
df = pd.read_excel(excel_path, sheet_name="Sheet1")
# 2. 检查必要的列
@@ -399,30 +436,63 @@ def generate_zodiac():
logger.error(f"Excel中找不到列名{date_column}】,请检查表头。")
return
# 检查是否存在"属相"列,不存在则新建
if target_column not in df.columns:
df[target_column] = ""
# --- 获取总行数,用于日志 ---
total_count = len(df)
logger.info(f"开始生成学生属相,共 {total_count} 位学生...")
# 3. 数据清洗与计算
temp_dates = pd.to_datetime(df[date_column], errors="coerce")
df[target_column] = temp_dates.apply(calculate_zodiac)
# 3. 预处理:将“生日”列转换为 datetime 格式
df['temp_date'] = pd.to_datetime(df[date_column], errors="coerce")
# 4. 遍历 DataFrame 并计算/更新数据
for i, row in df.iterrows():
# 关键点 1: 检查停止信号
if stop_event and stop_event.is_set():
logger.warning("任务已接收到停止信号,正在中断...")
return
# 添加进度条
if progress_callback:
progress_callback(i + 1, total_count, "生成属相")
name = row.get("姓名", f"学生_{i + 1}")
date = row['temp_date']
logger.info(f"[{i + 1}/{total_count}] 正在处理学生:{name}...")
# === 关键点 2: 检查生日是否为空 ===
if pd.isna(date):
# 记录警告日志并跳过当前循环迭代
logger.warning(f"跳过:学生【{name}】的生日数据为空或格式错误。")
# 可以选择将属相字段清空或设置为特定值,此处设置为“待补充”
df.loc[i, target_column] = "待补充"
continue # 跳到下一个学生
# =================================
# 5. 计算并赋值
zodiac = calculate_zodiac(date)
df.loc[i, target_column] = zodiac
logger.info(f" -> 属相计算成功:{name} ,属相: {zodiac}")
# 6. 清理和保存结果
df = df.drop(columns=['temp_date'])
# 5. 保存结果
save_path = excel_path
try:
df.to_excel(save_path, index=False)
df.to_excel(save_path, sheet_name="Sheet1", index=False)
logger.success(f"所有属相已更新并写入文件:{save_path}")
logger.warning(f"请检查文件 {save_path} 修改日期格式。")
except PermissionError:
logger.error(f"保存失败!请先关闭 Excel 文件:{save_path}")
# 添加进度条
if progress_callback:
progress_callback(total_count, total_count, "生成属相")
except FileNotFoundError:
logger.error(f"找不到文件 {config.get('excel_file')}")
logger.error(traceback.format_exc())
except Exception as e:
logger.error(f"程序运行出错: {str(e)}")
logger.error(traceback.format_exc())