【办公类-109-05】20250923插班生圆牌卡片改良01:一人2个圆牌(接送卡&被子卡&床卡&入园卡_word编辑单面)

背景需求:

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)}")

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

相关推荐
开开心心就好3 小时前
PDF清晰度提升工具,让模糊文档变清晰
java·服务器·前端·python·智能手机·pdf·ocr
AKAMAI3 小时前
部署Linode Kubernetes引擎企业版的三种方式
人工智能·云原生·云计算
wjt1020203 小时前
PyTorch 神经网络工具箱
人工智能·pytorch·神经网络
史锦彪3 小时前
yTorch 神经网络工具箱:核心原理与实践指南
人工智能·深度学习·神经网络
yzx9910133 小时前
对比django,flask,opencv三大
人工智能·后端·python·django·flask
码界筑梦坊3 小时前
194-基于Python的脑肿瘤患者数据分析可视化
开发语言·python·数据分析·sqlite·毕业设计·echarts·fastapi
海底的星星fly3 小时前
【Prompt学习技能树地图】DeepSeek专家模式下的Prompt工程进阶学习实践
人工智能·语言模型·prompt
Code_LT3 小时前
【算法】多榜单排序->综合排序问题
人工智能·算法
Thomas21433 小时前
MinMaxScaler Scikit-learn sparkml 稀疏向量
人工智能·机器学习·scikit-learn