
背景需求:
10天前,15号学生转学了,空出一个学额,20250923招生老师通知插班生来了,我打印了圆牌和方牌,发现要打印三张,但只要其中一小块。



塑封

塑封裁剪

全套资料打印

我发现插班生单独打印圆牌和卡牌有点浪费纸(一个一张,只要其中一小块。
因为圆牌需要两个(被子牌+接送牌),我暂时先将圆牌做一人两块。这样插班生只要打印1张纸(共3个人,每人两个圆牌)
python
# -*- coding:utf-8 -*-
'''
制作被子圆牌(word合并)多个班级-凑满整夜,如托班20人,凑满6*4页、中班30人,凑满5页,不存在的名字都是"张三"占位用,以后有插班生就可以自己修改名字了
预先再word里面页面背景-纹理,插入A4大小的图片背景(有6个园),
一个人两个牌子(被子圆牌、接送牌)
deepseek,豆包 阿夏
20250923
修改:每个名字制作2个相同的挂牌,一页3个名字*2
'''
import os
import pandas as pd
from docx import Document
from docx.shared import Cm
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.shared import Pt # 用于设置字体大小
from docx.oxml.ns import qn # 用于设置中文字体
from pypinyin import pinyin, Style
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
from docx.shared import RGBColor # 用于设置字体颜色
import shutil
import time
import re
# ========== 1. 核心配置(仅修改此部分路径) ==========
base_dir = r'c:\Users\jg2yXRZ\OneDrive\桌面\20250923文本框可编辑全校被子挂牌'
excel_file = os.path.join(base_dir, "班级信息表.xlsx") # Excel数据路径
template_word = os.path.join(base_dir, "挂牌.docx") # 现有3行2列表格模板
output_dir = os.path.join(base_dir, "零时文件夹") # 结果保存目录
editable_dir = os.path.join(base_dir, "20250923圆形挂牌(word编辑)" ) # 新建的可编辑文件夹
os.makedirs(output_dir, exist_ok=True) # 创建结果目录
os.makedirs(editable_dir, exist_ok=True) # 创建可编辑文件夹
# 每个名字制作的份数(一个名字两张卡,连在一起的
COPIES_PER_NAME = 2
# 定义各年级的固定人数(现在每个名字做2个,所以实际人数减半)
GRADE_SIZES = {
"托": 24*COPIES_PER_NAME, # 托班20人(凑满4张)
"小": 30*COPIES_PER_NAME, # 小班25人(5张)
"中": 30*COPIES_PER_NAME, # 中班30人(5张)
"大": 35*COPIES_PER_NAME # 大班35人(6张)
}
# ========== 2. Excel数据读取与拼音生成(保留核心逻辑) ==========
def read_excel_and_generate_pinyin(file_path):
"""读取Excel数据,生成带声调的姓名拼音,返回{班级名: 学生数据列表}"""
# 特殊姓氏读音校正(确保拼音准确性)
SPECIAL_SURNAMES = {
"乐": "Yuè", "单": "Shàn", "解": "Xiè", "查": "Zhā",
"盖": "Gě", "仇": "Qiú", "种": "Chóng", "朴": "Piáo",
"翟": "Zhái", "区": "Ōu", "繁": "Pó", "覃": "Qín",
"召": "Shào", "华": "Huà", "纪": "Jǐ", "曾": "Zēng",
"缪": "Miào", "员": "Yùn", "车": "Chē", "过": "Guō",
"尉": "Yù", "万": "Wàn"
}
COMPOUND_SURNAMES = {
"欧阳": "Ōu yáng", "上官": "Shàng guān", "皇甫": "Huáng fǔ",
"尉迟": "Yù chí", "万俟": "Mò qí", "长孙": "Zhǎng sūn",
"司徒": "Sī tú", "司空": "Sī kōng", "司徒": "Sī tú",
"诸葛": "Zhū gě", "东方": "Dōng fāng", "独孤": "Dú gū",
"慕容": "Mù róng", "宇文": "Yǔ wén"
}
def correct_name_pinyin(name):
"""生成带声调的姓名拼音(姓氏校正)"""
if not name or not isinstance(name, str):
return ""
name = name.strip()
# 优先处理复姓
for compound_surn in COMPOUND_SURNAMES:
if name.startswith(compound_surn):
surn_pinyin = COMPOUND_SURNAMES[compound_surn]
given_name = name[len(compound_surn):]
given_pinyin = ' '.join([p[0] for p in pinyin(given_name, style=Style.TONE)])
return f"{surn_pinyin} {given_pinyin}"
# 处理单姓多音字
first_char = name[0]
if first_char in SPECIAL_SURNAMES:
surn_pinyin = SPECIAL_SURNAMES[first_char]
given_name = name[1:]
given_pinyin = ' '.join([p[0] for p in pinyin(given_name, style=Style.TONE)])
return f"{surn_pinyin} {given_pinyin}"
# 普通姓名拼音
return ' '.join([p[0] for p in pinyin(name, style=Style.TONE)])
try:
# 读取所有工作表
excel = pd.ExcelFile(file_path)
sheet_names = excel.sheet_names
class_data = {}
for sheet_name in sheet_names:
# 跳过非班级工作表(如果有的话)
if not any(keyword in sheet_name for keyword in ["班", "class", "Class"]):
continue
df = pd.read_excel(file_path, sheet_name=sheet_name, usecols=range(5), header=None, dtype={0: str})
df = df.iloc[1:] # 跳过第1行标题
student_list = []
for idx, row in df.iterrows():
# 过滤无效数据(学号为空、姓名为空)
raw_id = str(row[0]).strip() if pd.notna(row[0]) else ""
name = str(row[3]).strip() if pd.notna(row[3]) else ""
if not raw_id.isdigit() or not name: # 如果没有学号,没有名字
name='张三'
elif not name: # 如果有学号,没有名字
name='张三'
# 提取核心信息
student_info = {
"campus": "XXX幼-" + (str(row[1]).strip() if pd.notna(row[1]) else ""), # 园区
"class_name": str(row[2]).strip() if pd.notna(row[2]) else "", # 班级
"student_id": f"{int(raw_id)}号", # 学号(如:1号)
"name": name, # 姓名
"name_pinyin": correct_name_pinyin(name) # 带声调拼音
}
student_list.append(student_info)
if student_list:
print(f"读取[{sheet_name}]:{len(student_list)}个有效学生")
class_data[sheet_name] = student_list
else:
print(f"工作表[{sheet_name}]无有效学生数据")
return class_data
except Exception as e:
print(f"Excel读取失败:{str(e)}")
return {}
def get_grade_size(class_name):
"""根据班级名称获取对应的固定人数"""
# 提取班级名称中的年级信息
if "托" in class_name:
return GRADE_SIZES["托"]
elif "小" in class_name:
return GRADE_SIZES["小"]
elif "中" in class_name:
return GRADE_SIZES["中"]
elif "大" in class_name:
return GRADE_SIZES["大"]
else:
# 默认返回小班人数
return GRADE_SIZES["小"]
def expand_student_list(student_list, target_size, class_name):
"""扩展学生列表到目标大小,用空学生填充"""
# 因为每个名字要做2份,所以实际需要的名字数量是目标大小的一半
name_count_needed = target_size // COPIES_PER_NAME
if len(student_list) >= name_count_needed:
return student_list[:name_count_needed] # 如果实际学生多于目标,截取前name_count_needed个
# 扩展学生列表
expanded_list = student_list.copy()
# 获取园区和班级名称(从第一个有效学生获取)
campus = student_list[0]["campus"] if student_list else "XXX幼"
class_name_full = student_list[0]["class_name"] if student_list else class_name
# 添加空学生
for i in range(len(student_list), name_count_needed):
empty_student = {
"campus": campus,
"class_name": class_name_full,
"student_id": f"{i+1}号",
"name": "张三", # 空姓名
"name_pinyin": "Zhang San" # 空拼音
}
expanded_list.append(empty_student)
return expanded_list
def duplicate_students_for_copies(student_list):
"""为每个学生复制COPIES_PER_NAME份"""
duplicated_list = []
for student in student_list:
for i in range(COPIES_PER_NAME):
duplicated_list.append(student.copy())
return duplicated_list
# ========== 3. 写入Word表格(3行2列,单个单元格含5种信息+回车) ==========
def write_student_to_word(student_list, template_path, output_path, class_name):
"""
将学生数据写入Word模板的3行2列表格
单个单元格内容:园区\n班级\n学号\n拼音\n姓名(按此顺序换行)
字体:微软雅黑;拼音大小:10pt;姓名大小:20pt;其他信息:14pt
颜色:班级、拼音、姓名为灰色,学号为黑色
"""
# 根据班级类型确定目标人数
target_size = get_grade_size(class_name)
print(f"班级 {class_name} 目标人数: {target_size}, 实际人数: {len(student_list)}")
# 扩展学生列表到目标大小(注意:现在扩展的是名字数量,不是总挂牌数)
expanded_student_list = expand_student_list(student_list, target_size, class_name)
# 为每个名字复制COPIES_PER_NAME份
final_student_list = duplicate_students_for_copies(expanded_student_list)
print(f"复制后总挂牌数: {len(final_student_list)} (每个名字{COPIES_PER_NAME}份)")
# 按6个学生一组分割(3行2列表格可容纳6个学生)
student_groups = [final_student_list[i:i+6] for i in range(0, len(final_student_list), 6)]
generated_files = [] # 存储生成的文件路径
for group_idx, group in enumerate(student_groups, 1):
# 复制模板文件(避免修改原模板)
temp_template = os.path.join(output_path, f"temp_{class_name}_{group_idx}.docx")
shutil.copy2(template_path, temp_template)
# 打开复制后的模板,操作表格
doc = Document(temp_template)
# 获取模板中的第一个表格(需确保"挂牌2.docx"的第一个表格是3行2列")
if not doc.tables:
print(f"模板{template_path}中未找到表格,跳过此组")
os.remove(temp_template)
continue
table = doc.tables[0]
# 清除表格原有内容
for row in table.rows:
for cell in row.cells:
for para in cell.paragraphs:
p = para._element
p.getparent().remove(p)
cell._element.clear() # 完全清空单元格
# 定义字体样式
def set_paragraph_style(para, text, font_size, is_bold=False, is_gray=True, underline=False):
"""设置段落的字体、大小、对齐(居中)和颜色"""
if not text: # 跳过空文本
return
run = para.add_run(text)
# 设置中文字体为微软雅黑
run.font.name = '微软雅黑'
run.element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
run.font.size = Pt(font_size)
run.font.bold = is_bold
run.font.underline = underline # 设置下划线
# 设置颜色:灰色或黑色
if is_gray:
run.font.color.rgb = RGBColor(169, 169, 169) # DarkGray
else:
run.font.color.rgb = RGBColor(0, 0, 0) # 黑色
para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 文字居中
# 逐个单元格写入学生信息(3行2列,共6个单元格)
cell_index = 0 # 单元格索引(0-5对应3行2列的6个单元格)
for row_idx, row in enumerate(table.rows):
for cell_idx, cell in enumerate(row.cells):
if cell_index >= len(group):
break # 学生不足6个时,空单元格跳过
# 添加这行代码:设置单元格垂直居中
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
# 当前学生的5种信息(按"园区→班级→学号→拼音→姓名"顺序")
student = group[cell_index]
info_lines = [
student["campus"], # 第1行:园区
student["class_name"], # 第2行:班级
student["student_id"], # 第3行:学号
student["name_pinyin"], # 第4行:拼音(10pt)
student["name"] # 第5行:姓名(20pt,加粗)
]
# 向单元格写入内容(每行单独设置字体大小和颜色)
for line_idx, line in enumerate(info_lines):
if line: # 只写入非空内容
para = cell.add_paragraph()
if line_idx == 0: # 园区:16pt
set_paragraph_style(para, line, font_size=16, is_bold=True, is_gray=True)
para.paragraph_format.line_spacing = Pt(30)
elif line_idx == 1: # 班级:30pt
set_paragraph_style(para, line, font_size=30, is_bold=True, is_gray=True)
para.paragraph_format.line_spacing = Pt(40)
elif line_idx == 2: # 学号:50pt
set_paragraph_style(para, line, font_size=50, is_bold=True, is_gray=False, underline=True)
para.paragraph_format.line_spacing = Pt(65)
elif line_idx == 3: # 拼音行:10pt
set_paragraph_style(para, line, font_size=10, is_bold=True, is_gray=True)
para.paragraph_format.line_spacing = Pt(25)
elif line_idx == 4: # 姓名行:30pt,加粗
set_paragraph_style(para, line, font_size=30, is_bold=True, is_gray=True)
para.paragraph_format.line_spacing = Pt(35)
cell_index += 1
# 保存当前组的Word文件
final_word_path = os.path.join(output_path, f"{class_name}_挂牌_{group_idx:02d}.docx")
doc.save(final_word_path)
generated_files.append(final_word_path) # 添加到生成文件列表
os.remove(temp_template) # 删除临时模板
print(f"生成第{group_idx}组Word:{os.path.basename(final_word_path)}")
return generated_files
def merge_docx_files_with_win32(template_file, files_list, output_filename):
"""使用win32com合并Word文档,去掉换页符"""
if not files_list:
print("没有文件可合并")
return
word = None
doc = None
try:
# 启动Word应用程序
import win32com.client as win32
word = win32.Dispatch("Word.Application")
word.Visible = False # 不显示Word界面
word.DisplayAlerts = False # 不显示警告
# 复制模板文件到输出文件
shutil.copy2(template_file, output_filename)
# 打开输出文件
doc = word.Documents.Open(output_filename)
# 将光标移动到文档末尾
selection = word.Selection
selection.EndKey(6) # wdStory = 6, 移动到文档末尾
# 逐个插入其他文件的内容(跳过第一个文件,因为已经是模板)
for i, file_path in enumerate(files_list):
if i == 0: # 跳过模板文件本身
continue
print(f"正在插入文件: {os.path.basename(file_path)}")
# 插入文件内容
selection.InsertFile(file_path)
# 移动到文档末尾准备插入下一个文件
selection.EndKey(6)
time.sleep(1) # 添加延时,确保Word有足够时间处理
# 保存合并后的文档
doc.Save()
print(f"已合并所有文档到:{output_filename}")
except Exception as e:
print(f"合并文档时出错:{e}")
# 尝试使用备选方法
try:
print("尝试使用备选合并方法...")
merge_docx_files_simple(template_file, files_list, output_filename)
except Exception as e2:
print(f"备选方法也失败:{e2}")
finally:
# 确保正确关闭文档和Word应用程序
try:
if doc:
doc.Close(False)
time.sleep(0.5)
except:
pass
try:
if word:
word.Quit()
time.sleep(0.5)
except:
pass
def merge_docx_files_simple(template_file, files_list, output_filename):
"""简单的合并方法:复制模板,然后逐个追加内容"""
# 复制模板文件
shutil.copy2(template_file, output_filename)
# 打开模板文件
main_doc = Document(output_filename)
# 逐个添加其他文件的内容
for i, file_path in enumerate(files_list):
if i == 0: # 跳过模板文件本身
continue
# 添加分页符
if i > 1: # 从第二个文件开始添加分页符
main_doc.add_page_break()
# 读取要添加的文件
sub_doc = Document(file_path)
# 复制表格内容
for table in sub_doc.tables:
# 创建新表格
new_table = main_doc.add_table(rows=len(table.rows), cols=len(table.columns))
# 复制内容和格式
for row_idx, row in enumerate(table.rows):
for col_idx, cell in enumerate(row.cells):
new_cell = new_table.cell(row_idx, col_idx)
# 清空新单元格
for paragraph in new_cell.paragraphs:
p = paragraph._element
p.getparent().remove(p)
new_cell._element.clear()
# 复制内容
for paragraph in cell.paragraphs:
new_para = new_cell.add_paragraph()
for run in paragraph.runs:
new_run = new_para.add_run(run.text)
# 复制字体样式
new_run.font.name = run.font.name
new_run.font.size = run.font.size
new_run.font.bold = run.font.bold
new_run.font.underline = run.font.underline
if hasattr(run.font.color, 'rgb'):
new_run.font.color.rgb = run.font.color.rgb
# 保存合并后的文档
main_doc.save(output_filename)
print(f"使用简单方法合并完成:{output_filename}")
def cleanup_directory(directory_path):
"""清理目录"""
try:
if os.path.exists(directory_path):
shutil.rmtree(directory_path)
print(f"已删除目录: {directory_path}")
except Exception as e:
print(f"删除目录时出错: {e}")
# ========== 4. 主程序(处理所有班级) ==========
if __name__ == "__main__":
# 步骤1:读取Excel数据(含拼音生成)
print("=== 开始读取Excel数据 ===")
class_data = read_excel_and_generate_pinyin(excel_file)
if not class_data:
print("无有效学生数据,程序退出")
exit()
# 为每个班级创建单独的输出目录
class_output_dirs = {}
for class_name in class_data.keys():
class_dir = os.path.join(output_dir, class_name)
os.makedirs(class_dir, exist_ok=True)
class_output_dirs[class_name] = class_dir
# 步骤2:遍历每个班级,写入Word表格
all_merged_files = []
for sheet_name, student_list in class_data.items():
# 获取班级名称(取第一个学生的班级信息)
class_name = student_list[0]["class_name"] if student_list else sheet_name
print(f"\n=== 处理班级:{class_name} ===")
# 步骤3:写入Word(3行2列表格,6个学生一组)
generated_files = write_student_to_word(
student_list=student_list,
template_path=template_word,
output_path=class_output_dirs[sheet_name],
class_name=class_name
)
# 步骤4:为每个班级合并所有生成的docx文件
if generated_files:
# 使用第一个文件作为模板
template_file = generated_files[0]
# 合并后的文件保存到可编辑文件夹
merged_filename = os.path.join(editable_dir, f"20250923_{class_name}_圆形牌_word编辑(接送&被子&床卡,一人两块圆牌).docx")
print(f"开始合并{class_name}的文档...")
try:
merge_docx_files_with_win32(template_file, generated_files, merged_filename)
all_merged_files.append(merged_filename)
print(f"{class_name}合并完成")
except Exception as e:
print(f"{class_name}的win32合并失败,尝试简单方法: {e}")
try:
merge_docx_files_simple(template_file, generated_files, merged_filename)
all_merged_files.append(merged_filename)
print(f"{class_name}使用简单方法合并完成")
except Exception as e2:
print(f"{class_name}的所有合并方法都失败: {e2}")
# 处理完一个班级后稍作延时,释放资源
time.sleep(2)
# 步骤5:清理临时文件
print("\n=== 清理临时文件 ===")
cleanup_directory(output_dir)
print(f"\n=== 所有处理完成 ===")
print(f"合并后的文件已保存到:{editable_dir}")
print(f"生成的合并文件:")
for file in all_merged_files:
print(f" - {os.path.basename(file)}")



后续我还要做一个插班生专用备份,一页包括两个圆牌、两个竖版长方、两个竖版长方卡,便于教师只修改这个,打印在一页上