【办公类-109-05】20250923插班生圆牌卡片改良01:一人2个圆牌(接送卡被子卡床卡入园卡_word编辑单面)
背景需求:
10天前,15号学生转学了,空出一个学额,20250923招生老师通知插班生来了,我打印了圆牌和方牌,发现要打印三张,但只要其中一小块。
塑封
塑封裁剪
全套资料打印
我发现插班生单独打印圆牌和卡牌有点浪费纸(一个一张,只要其中一小块。
因为圆牌需要两个(被子牌+接送牌),我暂时先将圆牌做一人两块。这样插班生只要打印1张纸(共3个人,每人两个圆牌)
# -*- 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_namesclass_data = {}for sheet_name in sheet_names:# 跳过非班级工作表(如果有的话)if not any(keyword in sheet_name for keyword in ["班", "class", "Class"]):continuedf = 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_listelse:print(f"工作表[{sheet_name}]无有效学生数据")return class_dataexcept 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_NAMEif 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_listdef 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)continuetable = doc.tables[0]# 清除表格原有内容for row in table.rows:for cell in row.cells:for para in cell.paragraphs:p = para._elementp.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: # 跳过空文本returnrun = 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_boldrun.font.underline = underline # 设置下划线# 设置颜色:灰色或黑色if is_gray:run.font.color.rgb = RGBColor(169, 169, 169) # DarkGrayelse: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: # 园区:16ptset_paragraph_style(para, line, font_size=16, is_bold=True, is_gray=True)para.paragraph_format.line_spacing = Pt(30)elif line_idx == 1: # 班级:30ptset_paragraph_style(para, line, font_size=30, is_bold=True, is_gray=True)para.paragraph_format.line_spacing = Pt(40)elif line_idx == 2: # 学号:50ptset_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: # 拼音行:10ptset_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_filesdef merge_docx_files_with_win32(template_file, files_list, output_filename):"""使用win32com合并Word文档,去掉换页符"""if not files_list:print("没有文件可合并")returnword = Nonedoc = Nonetry:# 启动Word应用程序import win32com.client as win32word = 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.Selectionselection.EndKey(6) # wdStory = 6, 移动到文档末尾# 逐个插入其他文件的内容(跳过第一个文件,因为已经是模板)for i, file_path in enumerate(files_list):if i == 0: # 跳过模板文件本身continueprint(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:passtry:if word:word.Quit()time.sleep(0.5)except:passdef 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._elementp.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.namenew_run.font.size = run.font.sizenew_run.font.bold = run.font.boldnew_run.font.underline = run.font.underlineif 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_nameprint(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)}")
后续我还要做一个插班生专用备份,一页包括两个圆牌、两个竖版长方、两个竖版长方卡,便于教师只修改这个,打印在一页上