)
背景需求电脑坏了需要重做《长方形挂牌》的模版、代码库根据最新一份长方形挂牌制作【办公类-109-09】20260228挂牌长方形挂牌卡片只有word编辑单面添加有效日期85*54mm横板和54*85mm竖版一页9张https://mp.csdn.net/mp_blog/creation/editor/158264646二、代码库制作把CSDN的代码复制进去修改参数把成果文件夹路径制作把包含所有幼儿信息的xlsx放入添加序号便于排序还需要做一个“横版”模版改名还需要做一个“竖版”模版改名三、模版制作参考这个文档里面的Word模版制作流程【办公类-109-04】20250807托小班新生的卡套长方形挂牌制作Python3.11.5 3285*54mm横板和54*85mm竖版一页9张https://mp.csdn.net/mp_blog/creation/editor/150009366一竖版3*3页边距2和2.4为了让表格能够在画布中间最后一行变成段间距0.7为了防止生成的内容下移到下一页最关键的设置‘’二横板3*3 页边距2和2.35部分中线隐藏为了防止生成的内容下移到下一页最关键的设置模版都做完了。四、运行代码 1.制作20250901托小班长方形卡片横版85*54mm和竖版54*85mm一页9张单面 2.添加姓名拼音显示功能拼音在姓名上方带声调 3.python 3.7.8 3. 横版布局单列显示园区和班级双列显示拼音、姓名和学号 4. 竖版布局单列显示所有信息 5. 添加照片占位符2.5cm宽*3.5cm高黑色框线中间写贴照片 6、生成全部班级或生成指定班级中1-中6班 7、添加日期2026.9-2027.6 豆包,deepseek,阿夏 20260901 # 记录程序开始时间 import time start_time time.time() # 创建照片占位符图片 import os from copy import deepcopy from PIL import Image, ImageDraw, ImageFont import shutil import pandas as pd from pypinyin import pinyin, Style from docx import Document from docx.shared import Cm, Pt from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.oxml import OxmlElement import glob from docx.oxml.shared import qn as oxml_qn from docx.oxml import parse_xml from docx.enum.text import WD_BREAK from docx.oxml.shared import qn from docx.oxml import parse_xml date2026.9—2027.6 dates202609 schoolXXX幼 def create_photo_placeholder(image_path): 创建照片占位符图片 try: # 确保目录存在 os.makedirs(os.path.dirname(image_path), exist_okTrue) # 设置图片尺寸 (2.5cm x 3.5cm) width, height int(2.5/2.54*300), int(3.5/2.54*300) # 转换为300dpi的像素 # 创建白色背景图片 img Image.new(RGB, (width, height), white) draw ImageDraw.Draw(img) # 绘制黑色边框 draw.rectangle([(10, 10), (width-10, height-10)], outlineblack, width2) # 添加文字 try: # 尝试使用微软雅黑字体 font ImageFont.truetype(msyh.ttc, 20) except: # 如果找不到字体使用默认字体 font ImageFont.load_default() text 贴照片 text_bbox draw.textbbox((0, 0), text, fontfont) text_width text_bbox[2] - text_bbox[0] text_height text_bbox[3] - text_bbox[1] text_x (width - text_width) // 2 text_y (height - text_height) // 2 draw.text((text_x, text_y), text, fillblack, fontfont) # 保存图片 img.save(image_path) print(f已创建照片占位符: {image_path}) return True except Exception as e: print(f创建照片占位符失败: {str(e)}) return False # 读取Excel数据 def read_excel_data(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ī mǎ, 诸葛: Zhū gě, 东方: Dōng fāng, 独孤: Dú gū, 慕容: Mù róng, 宇文: Yǔ wén } def correct_surname_pinyin(name): 校正姓氏拼音处理多音字问题 if not name or not isinstance(name, str) or name.strip() : return name name.strip() # 先检查复姓 for surname in COMPOUND_SURNAMES: if name.startswith(surname): surname_pinyin COMPOUND_SURNAMES[surname] given_name name[2:] given_pinyin .join([p[0] for p in pinyin(given_name, styleStyle.TONE)]) return f{surname_pinyin} {given_pinyin} # 检查单姓 first_char name[0] if first_char in SPECIAL_SURNAMES: surname_pinyin SPECIAL_SURNAMES[first_char] given_name name[1:] given_pinyin .join([p[0] for p in pinyin(given_name, styleStyle.TONE)]) return f{surname_pinyin} {given_pinyin} return .join([p[0] for p in pinyin(name, styleStyle.TONE)]) try: xls pd.ExcelFile(file_path) class_data {} for sheet_name in xls.sheet_names: df pd.read_excel( file_path, sheet_namesheet_name, usecolsrange(5), headerNone, dtype{0: str} ) df df.iloc[1:] data_list [] for index, row in df.iterrows(): row_num index 2 raw_id str(row[0]).strip() if pd.notna(row[0]) else # 如果没有学号但有名字或者有学号但没有名字都创建记录 name str(row[3]).strip() if pd.notna(row[3]) else try: # 处理学号 if raw_id.isdigit(): id_num int(raw_id) formatted_id f{id_num:02d} id_display f{id_num}号 else: # 如果没有学号使用行号作为学号 id_num index 1 formatted_id f{id_num:02d} id_display f{id_num}号 campus str(row[1]).strip() if pd.notna(row[1]) else class_name str(row[2]).strip() if pd.notna(row[2]) else gender str(row[4]).strip() if pd.notna(row[4]) else # 如果没有名字使用张三 if not name: name 张三 name_pinyin correct_surname_pinyin(name) data_list.append({ id_display: id_display, id_file: formatted_id, campus: campus, name: name, name_pinyin: name_pinyin, class_name: class_name, gender: gender }) except Exception as e: print(f处理第{row_num}行数据时出错: {str(e)}) continue if data_list: class_data[sheet_name] data_list return class_data except Exception as e: print(f读取Excel文件失败{str(e)}) return {} def expand_students_to_target_count(students, class_name): 将学生列表扩展到目标人数托小班27人中大班36人 # 确定目标人数 if 托 in class_name : target_count 27 elif 小 in class_name: target_count 27 elif 中 in class_name: target_count 36 else: target_count 36 # 如果实际人数已经达到或超过目标人数直接返回实际人数 if len(students) target_count: return students[:target_count] # 扩展学生列表 expanded_students students.copy() # 获取第一个学生的园区和班级信息用于填充空白学生 if students: base_campus students[0][campus] base_class students[0][class_name] else: base_campus base_class class_name # 添加空白学生 for i in range(len(students), target_count): empty_student { id_display: f{i1}号, id_file: f{i1:02d}, campus: base_campus, name: 张三, name_pinyin: Zhāng Sān, class_name: base_class, gender: } expanded_students.append(empty_student) return expanded_students def create_word_documents(class_data, orientation, temp_dir, photo_path): 创建Word文档横版或竖版 if orientation 横版: return create_horizontal_documents(class_data, temp_dir, photo_path) else: return create_vertical_documents(class_data, temp_dir, photo_path) def create_horizontal_documents(class_data, temp_dir, photo_path): 创建横版Word文档 # 模板文件路径 template_path os.path.join(path, 00 长方形挂牌_横版.docx) # 字体设置 font_name 微软雅黑 line_spacing_multiplier 1 for class_name, students in class_data.items(): # 按9人一组分组 groups [students[i:i9] for i in range(0, len(students), 9)] for group_num, group in enumerate(groups, 1): # 复制模板文件 file_name f{class_name}_横版_{group_num:02d}.docx file_path os.path.join(temp_dir, file_name) shutil.copy(template_path, file_path) # 打开文档 doc Document(file_path) if len(doc.tables) 0: print(f警告: {file_path} 中没有找到表格) continue table doc.tables[0] # 填充数据 for i, student in enumerate(group): row i // 3 col_pair i % 3 double_col 1 (col_pair * 2) single_col col_pair * 2 # 单列单元格园区和班级 single_cell table.cell(row, single_col) single_cell._element.clear_content() # 先插入照片 p_photo single_cell.add_paragraph() p_photo.alignment WD_PARAGRAPH_ALIGNMENT.CENTER try: run p_photo.add_run() run.add_picture(photo_path, widthCm(2.5), heightCm(3.5)) except Exception as e: print(f插入照片失败: {str(e)}) # 如果插入照片失败添加文字提示 run p_photo.add_run([照片占位符]) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(10) # 然后添加园区和班级信息 # 然后添加园区和班级信息 p_info single_cell.add_paragraph() p_info.alignment WD_PARAGRAPH_ALIGNMENT.CENTER p_pr p_info._element.get_or_add_pPr() spacing OxmlElement(w:spacing) spacing.set(qn(w:lineRule), auto) spacing.set(qn(w:line), str(int(240 * line_spacing_multiplier))) spacing.set(qn(w:before), 0) spacing.set(qn(w:after), 0) p_pr.append(spacing) # 学校园区 run p_info.add_run(f{school} {student[campus]}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(15) run.font.bold False # 日期 run p_info.add_run(f有效期{date}) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(10) # 小5 run.font.bold False # 双列单元格拼音、姓名和学号 double_cell table.cell(row, double_col) double_cell._element.clear_content() p_double double_cell.add_paragraph() p_double.alignment WD_PARAGRAPH_ALIGNMENT.CENTER p_pr p_double._element.get_or_add_pPr() spacing OxmlElement(w:spacing) spacing.set(qn(w:lineRule), auto) spacing.set(qn(w:line), str(int(240 * line_spacing_multiplier))) spacing.set(qn(w:before), 0) spacing.set(qn(w:after), 0) p_pr.append(spacing) run p_double.add_run(f{student[class_name]}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(20) run.font.bold True # 如果是多音字在拼音后面添加序号标识 pinyin_text student[name_pinyin] # if student[pinyin_total] 1: # # pinyin_text f ({student[pinyin_index]}/{student[pinyin_total]}) # pinyin_text f ({student[pinyin_index]}) run p_double.add_run(f{pinyin_text}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(12) run.font.bold False run p_double.add_run(f{student[name]}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(25) run.font.bold True run p_double.add_run(f{student[id_display]}) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(20) run.font.bold True doc.save(file_path) print(f已创建横版: {file_path}) return temp_dir def create_vertical_documents(class_data, temp_dir, photo_path): 创建竖版Word文档 # 模板文件路径 template_path os.path.join(path, 00 长方形挂牌_竖版.docx) # 字体设置 font_name 微软雅黑 line_spacing_multiplier 1 for class_name, students in class_data.items(): # 按9人一组分组 groups [students[i:i9] for i in range(0, len(students), 9)] for group_num, group in enumerate(groups, 1): # 复制模板文件 file_name f{class_name}_竖版_{group_num:02d}.docx file_path os.path.join(temp_dir, file_name) shutil.copy(template_path, file_path) # 打开文档 doc Document(file_path) if len(doc.tables) 0: print(f警告: {file_path} 中没有找到表格) continue table doc.tables[0] # 填充数据 for i, student in enumerate(group): row i // 3 col i % 3 cell table.cell(row, col) cell._element.clear_content() # 添加学校园区信息 p_campus cell.add_paragraph() p_campus.alignment WD_PARAGRAPH_ALIGNMENT.CENTER # 设置行距 p_pr p_campus._element.get_or_add_pPr() spacing OxmlElement(w:spacing) spacing.set(qn(w:lineRule), auto) spacing.set(qn(w:line), str(int(240 * line_spacing_multiplier))) spacing.set(qn(w:before), 0) spacing.set(qn(w:after), 0) p_pr.append(spacing) # 学校园区 run p_campus.add_run(f{school} {student[campus]}) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(15) run.font.bold False # 插入照片 p_photo cell.add_paragraph() p_photo.alignment WD_PARAGRAPH_ALIGNMENT.CENTER try: run p_photo.add_run() run.add_picture(photo_path, widthCm(2.5), heightCm(3.5)) except Exception as e: print(f插入照片失败: {str(e)}) # 如果插入照片失败添加文字提示 run p_photo.add_run([照片占位符]) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(10) # 添加其他信息 p_info cell.add_paragraph() p_info.alignment WD_PARAGRAPH_ALIGNMENT.CENTER # 设置行距 p_pr p_info._element.get_or_add_pPr() spacing OxmlElement(w:spacing) spacing.set(qn(w:lineRule), auto) spacing.set(qn(w:line), str(int(240 * line_spacing_multiplier))) spacing.set(qn(w:before), 0) spacing.set(qn(w:after), 0) p_pr.append(spacing) # 班级和学号一行 run p_info.add_run(f{student[class_name]}_) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(20) run.font.bold True run p_info.add_run(f{student[id_display]}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(20) run.font.bold True # 拼音 run p_info.add_run(f{student[name_pinyin]}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(12) run.font.bold False # 姓名 run p_info.add_run(f{student[name]}\n) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(25) run.font.bold True # 日期 run p_info.add_run(f有效期{date}) run.font.name font_name run._element.rPr.rFonts.set(qn(w:eastAsia), font_name) run.font.size Pt(10) run.font.bold False doc.save(file_path) print(f已创建竖版: {file_path}) return temp_dir def merge_word_documents(temp_dir, output_dir, orientation): 将同一班级的Word文档合并为一个Word文件 - 使用复制粘贴方法 # 获取所有Word文件并按班级分组 word_files glob.glob(os.path.join(temp_dir, f*_{orientation}_*.docx)) class_files {} for word_file in word_files: filename os.path.basename(word_file) class_name filename.split(_)[0] if class_name not in class_files: class_files[class_name] [] class_files[class_name].append(word_file) # 确保输出目录存在 os.makedirs(output_dir, exist_okTrue) for class_name, files in class_files.items(): files.sort() if not files: continue # 使用第一个文档作为基础 base_doc Document(files[0]) # 添加剩余文档的内容 for i in range(1, len(files)): source_doc Document(files[i]) # # 添加分页符 # base_doc.add_page_break() # 复制所有段落和表格 for element in source_doc.element.body: base_doc.element.body.append(deepcopy(element)) # 保存合并后的文档 output_file os.path.join(output_dir, f03_{dates}_{orientation}_长方形挂牌_word可编辑_{class_name}.docx) base_doc.save(output_file) print(f已合并{orientation}版Word: {output_file}) return output_dir def cleanup(temp_dir): 清理临时文件 try: shutil.rmtree(temp_dir) print(f已清理临时文件夹: {temp_dir}) except Exception as e: print(f清理临时文件夹失败: {str(e)}) # 主程序 if __name__ __main__: # 指定Excel文件路径 path rD:\Python最终内容\20260901长方形挂牌 excel_file os.path.join(path, 01 20260805班级信息表.xlsx) # # 指定要生成的班级列表中1班到中6班 # target_classes [中1班, 中2班, 中3班, 中4班, 中5班, 中6班] # 获取所有工作表名称排除第一个 try: xls pd.ExcelFile(excel_file) all_sheets xls.sheet_names # 排除第一个工作表通常是封面或说明本次就是从第一张表开始 if len(all_sheets) 1: target_classes all_sheets[0:] # 【1】从第二个工作表开始取所有 print(f将处理以下工作表: {target_classes}) else: print(错误: Excel文件中没有足够的工作表) exit(1) except Exception as e: print(f读取Excel文件失败: {str(e)}) exit(1) # 创建照片占位符 photo_path os.path.join(path, photo_placeholder.png) create_photo_placeholder(photo_path) # 创建临时目录 temp_dir os.path.join(path, 零时文件夹) os.makedirs(temp_dir, exist_okTrue) # 处理横版和竖版 orientations [横版, 竖版] for orientation in orientations: if orientation 横版: output_dir os.path.join(path, f{dates}_托小中大班新生_横版_卡片_可编辑) else: output_dir os.path.join(path, f{dates}_托小中大班新生_竖版_卡片_可编辑) # 确保输出目录存在 os.makedirs(output_dir, exist_okTrue) print(f当前处理方向: {orientation}, 输出目录: {output_dir}) # 调用读取函数 all_class_data read_excel_data(excel_file) if all_class_data: # 如果指定了target_classes则只处理这些班级否则处理所有班级 if target_classes in locals() and target_classes: class_data {} for class_name in target_classes: if class_name in all_class_data: class_data[class_name] all_class_data[class_name] print(f找到班级: {class_name}, 共{len(all_class_data[class_name])}人) else: print(f警告: 班级 {class_name} 在Excel文件中未找到) else: # 处理所有班级 class_data all_class_data print(f将处理所有{len(class_data)}个班级) if class_data: # 扩展学生到目标人数 expanded_class_data {} for class_name, students in class_data.items(): expanded_students expand_students_to_target_count(students, class_name) expanded_class_data[class_name] expanded_students # 创建Word文档使用扩展后的数据 temp_dir create_word_documents(expanded_class_data, orientation, temp_dir, photo_path) # 合并同一班级的Word文档 output_dir merge_word_documents(temp_dir, output_dir, orientation) # 打印汇总信息 print(\n处理完成汇总信息:) for class_name, students in class_data.items(): # 显示原始人数和扩展后人数 expanded_students expand_students_to_target_count(students, class_name) group_count (len(expanded_students) // 9 (1 if len(expanded_students) % 9 ! 0 else 0)) print(f{class_name}: 原始{len(students)}人 - 扩展到{len(expanded_students)}人分成{group_count}个文档) else: print(没有找到任何班级的数据) else: print(未能读取到任何班级数据请检查文件路径和内容格式。) # 清理临时文件 cleanup(temp_dir) # 计算并显示运行时间 end_time time.time() total_seconds end_time - start_time minutes int(total_seconds // 60) seconds int(total_seconds % 60) print(f\n程序运行时间: {minutes}分{seconds}秒 (共{total_seconds:.2f}秒))五、感悟这个横版和竖版的docx模版设计起来好麻烦各种小细节要调整才能保证最后的表格3*3都能显示在A4一页内。所以必须把Word模版上传CSDN“资源绑定”做备份