【办公类-109-13】20260901插班生专用名牌——“圆形挂牌3张”+长方形挂牌横1张竖1张”(word文本框图形+单元格合并)

背景需求

20250923我班招生老师通知插班生来了,我打印了圆牌和方牌,发现要打印三张,但只要其中一小块。好浪费纸张!

塑封裁剪

【办公类-109-05】20250923插班生圆形挂牌卡片改良01:一人2个圆牌(接送卡&被子卡&床卡&入园卡_word编辑单面)https://mp.csdn.net/mp_blog/creation/editor/151996771

20260819,这一天里我就转出了3位孩子,再做了13位中大班插班生(转入)的“Excel长表”。

二、需求分析

随着少子化,幼儿园孩子不再是学期初、学期末转入转出,几乎每月都有孩子转出,转入。

所以这“插班生名牌”就要单独制作,同时也要让班主任容易操作。

1.长方形挂牌打印很方便,没有问题

【办公类-109-12】20260228长方形挂牌卡片(电脑坏了,重制Word可编辑单面 )https://mp.csdn.net/mp_blog/creation/editor/163792872

 2.用的 Word插入背景图片的方法,生成了PDF(打印可以看见后面的圆形图片),但是如果教师自己修改信息后按打印,看见的就是没有圆边框,只有文字的效果

虽然我在文件名上标注了打印方法,但是教师一年就打印1、2次,根本不会记得复杂的打印设置流程

不过最近我也研究出了“透明填充的文本框图形覆盖”的方法,解决了打印的难题。

【办公类-109-12】20260901圆形挂牌(模版重制:文本框圆形重叠_word编辑单面_接送卡&被子卡&床卡&入园卡)https://mp.csdn.net/mp_blog/creation/editor/163796920

所以我可以尝试用A4设计模版,同时包含圆形挂牌和长方形挂牌,用批量替换的方式,便于教师单独制作一份“”插班生”专用的卡片。

三、素材制作(word模版单元格+文本框图形修改)

修改模版

合并单元格

拆分单元格

隐藏框线

写明“替换方法”

这个最重要!!!!

说明文字

四、制作Excel信息读取表

'''
制作班级名单
(第一行列名是学号 园区 班级 姓名 性别,总园包含大 1 - 大 5 班,总园包含中 1 - 中 6 班,一分园包含托 1 托 2 小 4 小 5,二分园包含托 3 小 1 小 2 小 3(有 “” 班 “”). 一个班级写一行。姓名全部写张三,学号全部写 23,性别全部写男,请把这个 Excel 写出来——
豆包,阿夏
20260821
'''

import os
import pandas as pd

# 构造数据列表
data_list = []

# 总园 大1‑大5班
for i in range(1, 6):
    data_list.append([23, "总园", f"大{i}班", "张三", "男"])

# 总园 中1‑中6班
for i in range(1, 7):
    data_list.append([23, "总园", f"中{i}班", "张三", "男"])

# 一分园班级
y1_classes = ["托1班", "托2班", "小4班", "小5班"]
for cls in y1_classes:
    data_list.append([23, "一分园", cls, "张三", "男"])

# 二分园班级
y2_classes = ["托3班", "小1班", "小2班", "小3班"]
for cls in y2_classes:
    data_list.append([23, "二分园", cls, "张三", "男"])

# 创建123文件夹,不存在就自动新建
folder_path = r"D:\Python最终内容\20260902插班生圆牌长方牌"
os.makedirs(folder_path, exist_ok=True)

# 转为DataFrame,写入Excel
columns = ["学号", "园区", "班级", "姓名", "性别"]
df = pd.DataFrame(data_list, columns=columns)
save_file = os.path.join(folder_path, "00 幼儿园班级名单张三.xlsx")
df.to_excel(save_file, index=False)

print(f"已保存到:{save_file}")
print(f"总数据行数:{len(data_list)}")

代码部分

把之前做的长方形挂牌和圆形挂牌都

两个附件上传(姓名删除)

它分析了很久很久

word打不开

WPS可以

1.圆牌里面的信息正确、

2.但是长方形牌子的字体都变了。也没有插入图片

3、所有的班级合并在一起,这次我希望拆分成单独的

其实只要把,“合并docx”和“清理文件夹”要隐藏,

# -*- coding: utf-8 -*-
'''
班级牌子样例生成器
基于5行5列Word模板,每页包含:
  - 3个圆牌(第1行左、第1行右、第2行左)
  - 1个竖版长方形(第2行列3,54×85mm)
  - 1个横版长方形(第4行列0-2,85×54mm)
每个班级一页,内容统一:张三,23号,zhāng sān
提取已有代码中圆牌、横版、竖版的内容写入逻辑重新组合
Python 3.7.8 兼容
豆包、阿夏
20260821
'''
import os
import time
import shutil
import pandas as pd
from copy import deepcopy
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

# ========== 记录程序开始时间 ==========
start_time = time.time()

# ========== 核心配置 ==========
date = '2026.9-2027.6'
school = 'XXX幼'
dates='202609'

# 固定内容(所有牌子统一)
FIXED_NAME = '张三'
FIXED_PINYIN = 'zhāng sān'
FIXED_ID = '23号'

# 路径配置(Windows环境请修改为实际路径)
base_dir = r'D:\Python最终内容\20260902插班生圆牌长方牌'
excel_file = os.path.join(base_dir, "01 20260805班级信息表.xlsx")
template_file = os.path.join(base_dir, "02 圆形挂牌模版_无框线.docx")
# output_dir = os.path.join(base_dir, "03_班级牌子样例_word可编辑")
temp_dir = os.path.join(base_dir, "03_班级牌子样例_word可编辑")

# os.makedirs(output_dir, exist_ok=True)
os.makedirs(temp_dir, exist_ok=True)

# 要生成的班级列表(留空则从Excel读取所有班级)
# 格式:[(园区, 班级名), ...]
# 例如:[('总园', '中1班'), ('总园', '小2班')]
TARGET_CLASSES = []  # 留空=全部班级


# ========== 字体样式工具函数(提取自已有代码)==========
def set_run_font(run, text, font_size, bold=False, color=None, font_name='微软雅黑', underline=False):
    """设置run的字体样式(提取自圆形挂牌代码)"""
    run.text = text
    run.font.name = font_name
    run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run.font.size = Pt(font_size)
    run.font.bold = bold
    run.font.underline = underline
    if color:
        run.font.color.rgb = RGBColor(*color)


def add_styled_paragraph(cell, text, font_size, bold=False, color=None,
                          alignment=WD_PARAGRAPH_ALIGNMENT.CENTER,
                          line_spacing=None, space_before=0, space_after=0):
    """在单元格中添加带样式的段落"""
    p = cell.add_paragraph()
    p.alignment = alignment
    if line_spacing:
        p.paragraph_format.line_spacing = Pt(line_spacing)
    p.paragraph_format.space_before = Pt(space_before)
    p.paragraph_format.space_after = Pt(space_after)
    run = p.add_run()
    set_run_font(run, text, font_size, bold, color)
    return p


def clear_cell(cell):
    """清空单元格内容"""
    cell._element.clear_content()


# ========== 圆牌内容写入(提取自圆形挂牌代码)==========
def write_circle_card(cell, campus, class_name):
    """
    写入圆牌内容(提取自 02圆形挂牌模版 代码的 set_paragraph_style 逻辑)
    内容顺序:园区 → 班级 → 学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    gray = (169, 169, 169)   # DarkGray
    black = (0, 0, 0)

    # 第1行:园区 16pt 加粗 灰色
    add_styled_paragraph(cell, f"{school}-{campus}", 16, bold=True, color=gray, line_spacing=45)
    # 第2行:班级 30pt 加粗 灰色
    add_styled_paragraph(cell, class_name, 30, bold=True, color=gray, line_spacing=35)
    # 第3行:学号 50pt 加粗 黑色 下划线
    add_styled_paragraph(cell, FIXED_ID, 50, bold=True, color=black, line_spacing=65)
    # 下划线单独设置
    for p in cell.paragraphs:
        for run in p.runs:
            if run.text == FIXED_ID:
                run.font.underline = True
    # 第4行:拼音 10pt 加粗 灰色
    add_styled_paragraph(cell, FIXED_PINYIN, 10, bold=True, color=gray, line_spacing=20)
    # 第5行:姓名 30pt 加粗 灰色
    add_styled_paragraph(cell, FIXED_NAME, 30, bold=True, color=gray, line_spacing=35)
    # 第6行:有效期 8pt 加粗 灰色
    add_styled_paragraph(cell, date, 8, bold=True, color=gray, line_spacing=15)


# ========== 竖版长方形内容写入(提取自长方形竖版代码)==========
def write_vertical_card(cell, campus, class_name):
    """
    写入竖版长方形内容(54×85mm,提取自 create_vertical_documents 逻辑)
    竖版布局:单列显示所有信息
    顺序:园区 → 照片占位 → 班级+学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    font_name = '微软雅黑'

    # 园区 15pt
    add_styled_paragraph(cell, f"{school} {campus}", 15, bold=False, line_spacing=24)

    # 照片占位(用文字提示,实际可替换为图片)
    p_photo = cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_photo.paragraph_format.line_spacing = Pt(20)
    run = p_photo.add_run()
    set_run_font(run, "[贴照片]", 10, bold=False, color=(169, 169, 169))

    # 班级+学号 20pt 加粗
    p_info = cell.add_paragraph()
    p_info.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_info.paragraph_format.line_spacing = Pt(24)
    run1 = p_info.add_run()
    set_run_font(run1, f"{class_name}_", 20, bold=True)
    run2 = p_info.add_run()
    set_run_font(run2, f"{FIXED_ID}\n", 20, bold=True)

    # 拼音 12pt
    add_styled_paragraph(cell, FIXED_PINYIN, 12, bold=False, line_spacing=20)
    # 姓名 25pt 加粗
    add_styled_paragraph(cell, FIXED_NAME, 25, bold=True, line_spacing=30)
    # 有效期 10pt
    add_styled_paragraph(cell, f"有效期:{date}", 10, bold=False, line_spacing=15)


# ========== 横版长方形内容写入(提取自长方形横版代码)==========
def write_horizontal_card(cell, campus, class_name):
    """
    写入横版长方形内容(85×54mm,提取自 create_horizontal_documents 逻辑)
    横版布局:左列(单列)显示园区和班级+日期,右列(双列)显示拼音、姓名和学号
    在单元格内用嵌套表格实现左右两列布局
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    font_name = '微软雅黑'

    # 在单元格内创建1行2列嵌套表格实现左右布局
    nested_table = cell.add_table(rows=1, cols=2)
    nested_table.autofit = False

    # 设置嵌套表格列宽(左列约3.5cm,右列约5.5cm)
    nested_table.columns[0].width = Cm(3.5)
    nested_table.columns[1].width = Cm(5.5)
    for row in nested_table.rows:
        row.cells[0].width = Cm(3.5)
        row.cells[1].width = Cm(5.5)

    left_cell = nested_table.cell(0, 0)
    right_cell = nested_table.cell(0, 1)

    left_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    right_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER

    # ===== 左列:照片 + 园区 + 日期 =====
    # 照片占位
    p_photo = left_cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_photo.paragraph_format.line_spacing = Pt(20)
    run = p_photo.add_run()
    set_run_font(run, "[贴照片]", 9, bold=False, color=(169, 169, 169))

    # 园区 15pt
    p_campus = left_cell.add_paragraph()
    p_campus.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_campus.paragraph_format.line_spacing = Pt(22)
    run = p_campus.add_run()
    set_run_font(run, f"{school} {campus}", 15, bold=False)

    # 日期 10pt
    p_date = left_cell.add_paragraph()
    p_date.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_date.paragraph_format.line_spacing = Pt(18)
    run = p_date.add_run()
    set_run_font(run, f"有效期:{date}", 10, bold=False)

    # ===== 右列:班级 + 拼音 + 姓名 + 学号 =====
    # 班级 20pt 加粗
    p_class = right_cell.add_paragraph()
    p_class.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_class.paragraph_format.line_spacing = Pt(24)
    run = p_class.add_run()
    set_run_font(run, class_name, 20, bold=True)

    # 拼音 12pt
    p_pinyin = right_cell.add_paragraph()
    p_pinyin.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pinyin.paragraph_format.line_spacing = Pt(20)
    run = p_pinyin.add_run()
    set_run_font(run, FIXED_PINYIN, 12, bold=False)

    # 姓名 25pt 加粗
    p_name = right_cell.add_paragraph()
    p_name.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_name.paragraph_format.line_spacing = Pt(28)
    run = p_name.add_run()
    set_run_font(run, FIXED_NAME, 25, bold=True)

    # 学号 20pt 加粗
    p_id = right_cell.add_paragraph()
    p_id.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_id.paragraph_format.line_spacing = Pt(22)
    run = p_id.add_run()
    set_run_font(run, FIXED_ID, 20, bold=True)


# ========== 从Excel读取班级列表 ==========
def get_classes_from_excel(file_path):
    """从Excel读取所有班级的(园区, 班级名),去重"""
    classes = []
    seen = set()
    xls = pd.ExcelFile(file_path)
    for sheet_name in xls.sheet_names:
        df = pd.read_excel(file_path, sheet_name=sheet_name, header=None, dtype=str)
        # 取第2行(索引1)的园区和班级
        if len(df) > 1:
            campus = str(df.iloc[1, 1]).strip() if pd.notna(df.iloc[1, 1]) else ''
            class_name = str(df.iloc[1, 2]).strip() if pd.notna(df.iloc[1, 2]) else sheet_name
            key = (campus, class_name)
            if campus and class_name and key not in seen:
                classes.append(key)
                seen.add(key)
    return classes


# ========== 为单个班级生成一页文档 ==========
def generate_class_page(campus, class_name, output_path):
    """
    基于模板为单个班级生成一页牌子文档
    填入5个牌子位置:3圆牌 + 1竖版 + 1横版
    """
    # 复制模板
    shutil.copy2(template_file, output_path)
    doc = Document(output_path)

    if len(doc.tables) == 0:
        print(f"  警告: 模板中没有找到表格")
        return False

    table = doc.tables[0]

    # ===== 第1步:合并第4行列0-2(横版长方形位置)=====
    # 第4行索引为4,列0、1、2需要合并
    try:
        cell_h = table.cell(4, 0).merge(table.cell(4, 1)).merge(table.cell(4, 2))
    except Exception as e:
        print(f"  合并横版单元格时出错: {e}")
        cell_h = table.cell(4, 0)

    # ===== 第2步:填入5个牌子内容 =====
    # 圆牌1:第1行左(列0-2已合并)→ cell(1,0)
    print(f"  填入 圆牌1(第1行左)")
    write_circle_card(table.cell(1, 0), campus, class_name)

    # 圆牌2:第1行右(列3-4已合并)→ cell(1,3)
    print(f"  填入 圆牌2(第1行右)")
    write_circle_card(table.cell(1, 3), campus, class_name)

    # 圆牌3:第2行左(列0-2已合并)→ cell(2,0)
    print(f"  填入 圆牌3(第2行左)")
    write_circle_card(table.cell(2, 0), campus, class_name)

    # 竖版长方形:第2行列3 → cell(2,3),尺寸5.4cm宽×8.5cm高
    print(f"  填入 竖版长方形(第2行列3)")
    write_vertical_card(table.cell(2, 3), campus, class_name)

    # 横版长方形:第4行列0-2(已合并)→ cell_h,高5.4cm
    print(f"  填入 横版长方形(第4行列0-2)")
    write_horizontal_card(cell_h, campus, class_name)

    # 保存
    doc.save(output_path)
    print(f"  已生成: {os.path.basename(output_path)}")
    return True


# ========== 合并多个班级页面到一个文档 ==========
def merge_class_pages(file_list, output_filename):
    """
    合并多个单页Word文档为一个文档(每页一个班级)
    使用复制body元素的方法,保留表格格式
    """
    if not file_list:
        print("没有文件可合并")
        return

    # 以第一个文件为基础
    shutil.copy2(file_list[0], output_filename)
    base_doc = Document(output_filename)

    for i in range(1, len(file_list)):
        source_doc = Document(file_list[i])

        # 添加分页符
        base_doc.add_page_break()

        # 复制源文档的所有body元素(段落和表格)
        for element in source_doc.element.body:
            base_doc.element.body.append(deepcopy(element))

    base_doc.save(output_filename)
    print(f"已合并 {len(file_list)} 个班级页面到: {os.path.basename(output_filename)}")


# ========== 清理临时文件 ==========
def cleanup(temp_path):
    try:
        if os.path.exists(temp_path):
            shutil.rmtree(temp_path)
            print(f"已清理临时文件夹: {temp_path}")
    except Exception as e:
        print(f"清理临时文件夹失败: {e}")


# ========== 主程序 ==========
if __name__ == "__main__":
    print("=" * 60)
    print("班级牌子样例生成器")
    print("每页包含:3个圆牌 + 1个竖版长方形 + 1个横版长方形")
    print(f"统一内容:{FIXED_NAME} {FIXED_ID} {FIXED_PINYIN}")
    print("=" * 60)

    # 步骤1:获取班级列表
    if TARGET_CLASSES:
        classes = TARGET_CLASSES
        print(f"\n使用指定班级列表(共{len(classes)}个):")
    else:
        if os.path.exists(excel_file):
            classes = get_classes_from_excel(excel_file)
            print(f"\n从Excel读取到 {len(classes)} 个班级:")
        else:
            print(f"错误: 找不到Excel文件 {excel_file}")
            exit(1)

    for campus, class_name in classes:
        print(f"  - {campus} {class_name}")

    # 步骤2:为每个班级生成单页文档
    print(f"\n{'=' * 60}")
    print("开始生成每个班级的单页文档...")
    print("=" * 60)

    temp_files = []
    for idx, (campus, class_name) in enumerate(classes, 1):
        print(f"\n[{idx}/{len(classes)}] 处理: {campus} {class_name}")
        temp_file = os.path.join(temp_dir, f"04_{dates}_{class_name}_插班生专用_圆牌长方牌打印.docx")
        success = generate_class_page(campus, class_name, temp_file)
        if success:
            temp_files.append(temp_file)

    # # 步骤3:合并所有班级页面
    # print(f"\n{'=' * 60}")
    # print("合并所有班级页面...")
    # print("=" * 60)

    # if temp_files:
    #     merged_file = os.path.join(
    #         output_dir,
    #         f"03_班级牌子样例_3圆牌+横版+竖版_共{len(classes)}班_张三23号.docx"
    #     )
    #     merge_class_pages(temp_files, merged_file)
    #     print(f"\n最终输出: {merged_file}")
    # else:
    #     print("没有生成任何班级页面")

    # 步骤4:清理临时文件
    # cleanup(temp_dir)

    # 步骤5:输出运行时间
    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}秒)")
    print(f"共生成 {len(classes)} 个班级的牌子样例页")

下面的表格缺了一条线

我反复检查,原始模版里面横版有边框黑线



# -*- coding: utf-8 -*-
'''
班级牌子样例生成器
基于5行5列Word模板,每页包含:
  - 3个圆牌(第1行左、第1行右、第2行左)
  - 1个竖版长方形(第2行列3,54×85mm)
  - 1个横版长方形(第4行列0-2,85×54mm)
每个班级一页,内容统一:张三,23号,zhāng sān
提取已有代码中圆牌、横版、竖版的内容写入逻辑重新组合
Python 3.7.8 兼容
豆包、阿夏
20260821
'''
import os
import time
import shutil
import pandas as pd
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement


# ========== 记录程序开始时间 ==========
start_time = time.time()


# ========== 核心配置 ==========
date = '2026.9-2027.6'
school = 'XXX幼'
dates='202609'


# 固定内容(所有牌子统一)
FIXED_NAME = '张三'
FIXED_PINYIN = 'zhāng sān'
FIXED_ID = '23号'


# 路径配置(Windows环境请修改为实际路径)
base_dir = r'D:\Python最终内容\20260902插班生圆牌长方牌'
excel_file = os.path.join(base_dir, "01 20260805班级信息表.xlsx")
template_file = os.path.join(base_dir, "02 圆形挂牌模版_无框线.docx")
# output_dir = os.path.join(base_dir, "03_班级牌子样例_word可编辑")
temp_dir = os.path.join(base_dir, "03_班级牌子样例_word可编辑")

os.makedirs(temp_dir, exist_ok=True)

# 要生成的班级列表(留空则从Excel读取所有班级)
# 格式:[(园区, 班级名), ...]
# 例如:[('总园', '中1班'), ('总园', '小2班')]
TARGET_CLASSES = []  # 留空=全部班级


# ========== 【新增修复函数:强制设置单元格右边框黑色实线,解决merge丢失右边框bug】==========
def set_cell_right_border(cell):
    """给单元格设置黑色实线右边框;修复merge合并单元格后右侧框线消失问题"""
    tc_pr = cell._element.get_or_add_tcPr()
    tc_borders = tc_pr.find(qn("w:tcBorders"))
    if tc_borders is None:
        tc_borders = OxmlElement("w:tcBorders")
        tc_pr.append(tc_borders)
    # 删除旧的right节点
    old_right = tc_borders.find(qn("w:right"))
    if old_right is not None:
        tc_borders.remove(old_right)
    # 新建黑色实线右边框 sz=4 → 0.5磅
    right_elem = OxmlElement("w:right")
    right_elem.set(qn("w:val"), "single")
    right_elem.set(qn("w:sz"), "4")
    right_elem.set(qn("w:color"), "000000")
    right_elem.set(qn("w:space"), "0")
    tc_borders.append(right_elem)


# ========== 字体样式工具函数(提取自已有代码)==========
def set_run_font(run, text, font_size, bold=False, color=None, font_name='微软雅黑', underline=False):
    """设置run的字体样式(提取自圆形挂牌代码)"""
    run.text = text
    run.font.name = font_name
    run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run.font.size = Pt(font_size)
    run.font.bold = bold
    run.font.underline = underline
    if color:
        run.font.color.rgb = RGBColor(*color)


def add_styled_paragraph(cell, text, font_size, bold=False, color=None,
                        alignment=WD_PARAGRAPH_ALIGNMENT.CENTER,
                        line_spacing=None, space_before=0, space_after=0):
    """在单元格中添加带样式的段落"""
    p = cell.add_paragraph()
    p.alignment = alignment
    if line_spacing:
        p.paragraph_format.line_spacing = Pt(line_spacing)
    p.paragraph_format.space_before = Pt(space_before)
    p.paragraph_format.space_after = Pt(space_after)
    run = p.add_run()
    set_run_font(run, text, font_size, bold, color)
    return p


def clear_cell(cell):
    """清空单元格内容"""
    cell._element.clear_content()


# ========== 圆牌内容写入(提取自圆形挂牌代码)==========
def write_circle_card(cell, campus, class_name):
    """
    写入圆牌内容(提取自 02圆形挂牌模版 代码的 set_paragraph_style 逻辑)
    内容顺序:园区 → 班级 → 学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    gray = (169, 169, 169)   # DarkGray
    black = (0, 0, 0)

    # 第1行:园区 16pt 加粗 灰色
    add_styled_paragraph(cell, f"{school}-{campus}", 16, bold=True, color=gray, line_spacing=45)
    # 第2行:班级 30pt 加粗 灰色
    add_styled_paragraph(cell, class_name, 30, bold=True, color=gray, line_spacing=35)
    # 第3行:学号 50pt 加粗 黑色 下划线
    add_styled_paragraph(cell, FIXED_ID, 50, bold=True, color=black, line_spacing=65)
    # 下划线单独设置
    for p in cell.paragraphs:
        for run in p.runs:
            if run.text == FIXED_ID:
                run.font.underline = True
    # 第4行:拼音 10pt 加粗 灰色
    add_styled_paragraph(cell, FIXED_PINYIN, 10, bold=True, color=gray, line_spacing=20)
    # 第5行:姓名 30pt 加粗 灰色
    add_styled_paragraph(cell, FIXED_NAME, 30, bold=True, color=gray, line_spacing=35)
    # 第6行:有效期 8pt 加粗 灰色
    add_styled_paragraph(cell, date, 8, bold=True, color=gray, line_spacing=15)


# ========== 竖版长方形内容写入(提取自长方形竖版代码)==========
def write_vertical_card(cell, campus, class_name):
    """
    写入竖版长方形内容(54×85mm,提取自 create_vertical_documents 逻辑)
    竖版布局:单列显示所有信息
    顺序:园区 → 照片占位 → 班级+学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    # 园区 15pt
    add_styled_paragraph(cell, f"{school} {campus}", 15, bold=False, line_spacing=24)

    # 照片占位(用文字提示,实际可替换为图片)
    p_photo = cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_photo.paragraph_format.line_spacing = Pt(20)
    run = p_photo.add_run()
    set_run_font(run, "[贴照片]", 10, bold=False, color=(169, 169, 169))

    # 班级+学号 20pt 加粗
    p_info = cell.add_paragraph()
    p_info.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_info.paragraph_format.line_spacing = Pt(24)
    run1 = p_info.add_run()
    set_run_font(run1, f"{class_name}_", 20, bold=True)
    run2 = p_info.add_run()
    set_run_font(run2, f"{FIXED_ID}\n", 20, bold=True)

    # 拼音 12pt
    add_styled_paragraph(cell, FIXED_PINYIN, 12, bold=False, line_spacing=20)
    # 姓名 25pt 加粗
    add_styled_paragraph(cell, FIXED_NAME, 25, bold=True, line_spacing=30)
    # 有效期 10pt
    add_styled_paragraph(cell, f"有效期:{date}", 10, bold=False, line_spacing=15)


# ========== 横版长方形内容写入(提取自长方形横版代码)==========
def write_horizontal_card(cell, campus, class_name):
    """
    写入横版长方形内容(85×54mm,提取自 create_horizontal_documents 逻辑)
    横版布局:左列(单列)显示园区和班级+日期,右列(双列)显示拼音、姓名和学号
    在单元格内用嵌套表格实现左右两列布局
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    # 在单元格内创建1行2列嵌套表格实现左右布局
    nested_table = cell.add_table(rows=1, cols=2)
    nested_table.autofit = False

    # 设置嵌套表格列宽(左列约3.5cm,右列约5.5cm)
    nested_table.columns[0].width = Cm(3.5)
    nested_table.columns[1].width = Cm(5.5)
    for row in nested_table.rows:
        row.cells[0].width = Cm(3.5)
        row.cells[1].width = Cm(5.5)

    left_cell = nested_table.cell(0, 0)
    right_cell = nested_table.cell(0, 1)

    left_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    right_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER

    # ===== 左列:照片 + 园区 + 日期 =====
    # 照片占位
    p_photo = left_cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_photo.paragraph_format.line_spacing = Pt(20)
    run = p_photo.add_run()
    set_run_font(run, "[贴照片]", 9, bold=False, color=(169, 169, 169))

    # 园区 15pt
    p_campus = left_cell.add_paragraph()
    p_campus.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_campus.paragraph_format.line_spacing = Pt(22)
    run = p_campus.add_run()
    set_run_font(run, f"{school} {campus}", 15, bold=False)

    # 日期 10pt
    p_date = left_cell.add_paragraph()
    p_date.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_date.paragraph_format.line_spacing = Pt(18)
    run = p_date.add_run()
    set_run_font(run, f"有效期:{date}", 10, bold=False)

    # ===== 右列:班级 + 拼音 + 姓名 + 学号 =====
    # 班级 20pt 加粗
    p_class = right_cell.add_paragraph()
    p_class.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_class.paragraph_format.line_spacing = Pt(24)
    run = p_class.add_run()
    set_run_font(run, class_name, 20, bold=True)

    # 拼音 12pt
    p_pinyin = right_cell.add_paragraph()
    p_pinyin.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pinyin.paragraph_format.line_spacing = Pt(20)
    run = p_pinyin.add_run()
    set_run_font(run, FIXED_PINYIN, 12, bold=False)

    # 姓名 25pt 加粗
    p_name = right_cell.add_paragraph()
    p_name.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_name.paragraph_format.line_spacing = Pt(28)
    run = p_name.add_run()
    set_run_font(run, FIXED_NAME, 25, bold=True)

    # 学号 20pt 加粗
    p_id = right_cell.add_paragraph()
    p_id.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_id.paragraph_format.line_spacing = Pt(22)
    run = p_id.add_run()
    set_run_font(run, FIXED_ID, 20, bold=True)


# ========== 从Excel读取班级列表 ==========
def get_classes_from_excel(file_path):
    """从Excel读取所有班级的(园区, 班级名),去重"""
    classes = []
    seen = set()
    xls = pd.ExcelFile(file_path)
    for sheet_name in xls.sheet_names:
        df = pd.read_excel(file_path, sheet_name=sheet_name, header=None, dtype=str)
        # 取第2行(索引1)的园区和班级
        if len(df) > 1:
            campus = str(df.iloc[1, 1]).strip() if pd.notna(df.iloc[1, 1]) else ''
            class_name = str(df.iloc[1, 2]).strip() if pd.notna(df.iloc[1, 2]) else sheet_name
            key = (campus, class_name)
            if campus and class_name and key not in seen:
                classes.append(key)
                seen.add(key)
    return classes


# ========== 为单个班级生成一页文档 ==========
def generate_class_page(campus, class_name, output_path):
    """
    基于模板为单个班级生成一页牌子文档
    填入5个牌子位置:3圆牌 + 1竖版 + 1横版
    """
    # 复制模板
    shutil.copy2(template_file, output_path)
    doc = Document(output_path)

    if len(doc.tables) == 0:
        print(f"  警告: 模板中没有找到表格")
        return False

    table = doc.tables[0]

    # ===== 第1步:合并第4行列0-2(横版长方形位置)=====
    # 第4行索引为4,列0、1、2需要合并
    try:
        cell_h = table.cell(4, 0).merge(table.cell(4, 1)).merge(table.cell(4, 2))
        # =========【修复!合并完成立刻恢复右边框】=========
        set_cell_right_border(cell_h)
    except Exception as e:
        print(f"  合并横版单元格时出错: {e}")
        cell_h = table.cell(4, 0)
        set_cell_right_border(cell_h)

    # ===== 第2步:填入5个牌子内容 =====
    # 圆牌1:第1行左(列0-2已合并)→ cell(1,0)
    print(f"  填入 圆牌1(第1行左)")
    write_circle_card(table.cell(1, 0), campus, class_name)

    # 圆牌2:第1行右(列3-4已合并)→ cell(1,3)
    print(f"  填入 圆牌2(第1行右)")
    write_circle_card(table.cell(1, 3), campus, class_name)

    # 圆牌3:第2行左(列0-2已合并)→ cell(2,0)
    print(f"  填入 圆牌3(第2行左)")
    write_circle_card(table.cell(2, 0), campus, class_name)

    # 竖版长方形:第2行列3 → cell(2,3),尺寸5.4cm宽×8.5cm高
    print(f"  填入 竖版长方形(第2行列3)")
    write_vertical_card(table.cell(2, 3), campus, class_name)

    # 横版长方形:第4行列0-2(已合并)→ cell_h,高5.4cm
    print(f"  填入 横版长方形(第4行列0-2)")
    write_horizontal_card(cell_h, campus, class_name)

    # 保存
    doc.save(output_path)
    print(f"  已生成: {os.path.basename(output_path)}")
    return True


# ========== 清理临时文件 ==========
def cleanup(temp_path):
    try:
        if os.path.exists(temp_path):
            shutil.rmtree(temp_path)
            print(f"已清理临时文件夹: {temp_path}")
    except Exception as e:
        print(f"清理临时文件夹失败: {e}")


# ========== 主程序 ==========
if __name__ == "__main__":
    print("=" * 60)
    print("班级牌子样例生成器")
    print("每页包含:3个圆牌 + 1个竖版长方形 + 1个横版长方形")
    print(f"统一内容:{FIXED_NAME} {FIXED_ID} {FIXED_PINYIN}")
    print("=" * 60)

    # 步骤1:获取班级列表
    if TARGET_CLASSES:
        classes = TARGET_CLASSES
        print(f"\n使用指定班级列表(共{len(classes)}个):")
    else:
        if os.path.exists(excel_file):
            classes = get_classes_from_excel(excel_file)
            print(f"\n从Excel读取到 {len(classes)} 个班级:")
        else:
            print(f"错误: 找不到Excel文件 {excel_file}")
            exit(1)

    for campus, class_name in classes:
        print(f"  - {campus} {class_name}")

    # 步骤2:为每个班级生成单页文档
    print(f"\n{'=' * 60}")
    print("开始生成每个班级的单页文档...")
    print("=" * 60)

    temp_files = []
    for idx, (campus, class_name) in enumerate(classes, 1):
        print(f"\n[{idx}/{len(classes)}] 处理: {campus} {class_name}")
        temp_file = os.path.join(temp_dir, f"04_{dates}_{class_name}_插班生专用_圆牌长方牌打印.docx")
        success = generate_class_page(campus, class_name, temp_file)
        if success:
            temp_files.append(temp_file)
    # # 步骤3:合并所有班级页面
    # print(f"\n{'=' * 60}")
    # print("合并所有班级页面...")
    # print("=" * 60)

    # if temp_files:
    #     merged_file = os.path.join(
    #         output_dir,
    #         f"03_班级牌子样例_3圆牌+横版+竖版_共{len(classes)}班_张三23号.docx"
    #     )
    #     merge_class_pages(temp_files, merged_file)
    #     print(f"\n最终输出: {merged_file}")
    # else:
    #     print("没有生成任何班级页面")

    # 步骤4:清理临时文件
    # cleanup(temp_dir)

    

    # 步骤5:输出运行时间
    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}秒)")
    print(f"共生成 {len(classes)} 个班级的牌子样例页")

接下去解决 长方形横版和竖版没有照片、格式间距错误的问题

改了无网格,字间距就缩小了

横版还有点问题

应该是4行0列 4行1列

运行后成功

# -*- coding: utf-8 -*-
'''
班级牌子样例生成器
基于5行5列Word模板,每页包含:
  - 3个圆牌(第1行左、第1行右、第2行左)
  - 1个竖版长方形(第2行列3,54×85mm)
  - 1个横版长方形(第4行列0-2,85×54mm)
每个班级一页,内容统一:张三,23号,zhāng sān
提取已有代码中圆牌、横版、竖版的内容写入逻辑重新组合
增加多音字、复姓拼音校正功能
Python 3.7.8 兼容
豆包、阿夏
20260821
'''
import os
import time
import shutil
from PIL import Image, ImageDraw, ImageFont
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from pypinyin import pinyin, Style


# ========== 记录程序开始时间 ==========
start_time = time.time()


# ========== 创建照片占位符图片 ==========
def create_photo_placeholder(image_path):
    """创建照片占位符图片:2.5cm宽*3.5cm高,黑色框线,中间写"贴照片"""
    try:
        # 确保目录存在
        os.makedirs(os.path.dirname(image_path), exist_ok=True)

        # 设置图片尺寸 (2.5cm x 3.5cm) 转换为300dpi的像素
        width, height = int(2.5 / 2.54 * 300), int(3.5 / 2.54 * 300)

        # 创建白色背景图片
        img = Image.new('RGB', (width, height), 'white')
        draw = ImageDraw.Draw(img)

        # 绘制黑色边框
        draw.rectangle([(10, 10), (width - 10, height - 10)], outline='black', width=2)

        # 添加文字
        try:
            # 尝试使用微软雅黑字体
            font = ImageFont.truetype("msyh.ttc", 20)
        except:
            # 如果找不到字体,使用默认字体
            font = ImageFont.load_default()

        text = "贴照片"
        text_bbox = draw.textbbox((0, 0), text, font=font)
        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, fill='black', font=font)

        # 保存图片
        img.save(image_path)
        print(f"已创建照片占位符: {image_path}")
        return True
    except Exception as e:
        print(f"创建照片占位符失败: {str(e)}")
        return False


# ========== 多音字、复姓拼音校正(从原始长方形卡片代码复制) ==========
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 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)])


# ========== 核心配置 ==========
date = '2026.9-2027.6'
school = '景谷二幼'
dates = '202609'

# 固定内容(所有牌子统一)
FIXED_NAME = '张三'
FIXED_ID = '23号'
FIXED_PINYIN = correct_surname_pinyin(FIXED_NAME)   # 【多音字函数动态生成拼音】

# 路径配置(Windows环境请修改为实际路径)
base_dir = r'D:\Python最终内容\20260902插班生圆牌长方牌'
excel_file = os.path.join(base_dir, "01 20260805班级信息表.xlsx")
template_file = os.path.join(base_dir, "02 插班生专用(圆形+长方形)挂牌模版_无框线.docx")
temp_dir = os.path.join(base_dir, "03_班级牌子样例_word可编辑")
photo_path = os.path.join(base_dir, "photo_placeholder.png")

os.makedirs(temp_dir, exist_ok=True)

# 要生成的班级列表(留空则从Excel读取所有班级)
# 格式:[(园区, 班级名), ...]
# 例如:[('总园', '中1班'), ('总园', '小2班')]
TARGET_CLASSES = []  # 留空=全部班级


# ========== 【修复函数:强制设置单元格右边框黑色实线,解决merge丢失右边框bug】==========
def set_cell_right_border(cell):
    """给单元格设置黑色实线右边框;修复merge合并单元格后右侧框线消失问题"""
    tc_pr = cell._element.get_or_add_tcPr()
    tc_borders = tc_pr.find(qn("w:tcBorders"))
    if tc_borders is None:
        tc_borders = OxmlElement("w:tcBorders")
        tc_pr.append(tc_borders)
    # 删除旧的right节点
    old_right = tc_borders.find(qn("w:right"))
    if old_right is not None:
        tc_borders.remove(old_right)
    # 新建黑色实线右边框 sz=4 → 0.5磅
    right_elem = OxmlElement("w:right")
    right_elem.set(qn("w:val"), "single")
    right_elem.set(qn("w:sz"), "4")
    right_elem.set(qn("w:color"), "000000")
    right_elem.set(qn("w:space"), "0")
    tc_borders.append(right_elem)


# ========== 字体样式工具函数(提取自已有代码)==========
def set_run_font(run, text, font_size, bold=False, color=None, font_name='微软雅黑', underline=False):
    """设置run的字体样式(提取自圆形挂牌代码)"""
    run.text = text
    run.font.name = font_name
    run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run.font.size = Pt(font_size)
    run.font.bold = bold
    run.font.underline = underline
    if color:
        run.font.color.rgb = RGBColor(*color)


def add_styled_paragraph(cell, text, font_size, bold=False, color=None,
                        alignment=WD_PARAGRAPH_ALIGNMENT.CENTER,
                        line_spacing=None, space_before=0, space_after=0):
    """在单元格中添加带样式的段落"""
    p = cell.add_paragraph()
    p.alignment = alignment
    if line_spacing:
        p.paragraph_format.line_spacing = Pt(line_spacing)
    p.paragraph_format.space_before = Pt(space_before)
    p.paragraph_format.space_after = Pt(space_after)
    run = p.add_run()
    set_run_font(run, text, font_size, bold, color)
    return p


def clear_cell(cell):
    """清空单元格内容"""
    cell._element.clear_content()


# ========== 圆牌内容写入(提取自圆形挂牌代码)==========
def write_circle_card(cell, campus, class_name):
    """
    写入圆牌内容(提取自 02圆形挂牌模版 代码的 set_paragraph_style 逻辑)
    内容顺序:园区 → 班级 → 学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    gray = (169, 169, 169)   # DarkGray
    black = (0, 0, 0)

    # 第1行:园区 16pt 加粗 灰色
    add_styled_paragraph(cell, f"{school}-{campus}", 16, bold=True, color=gray, line_spacing=45)
    # 第2行:班级 30pt 加粗 灰色
    add_styled_paragraph(cell, class_name, 30, bold=True, color=gray, line_spacing=35)
    # 第3行:学号 50pt 加粗 黑色 下划线
    add_styled_paragraph(cell, FIXED_ID, 50, bold=True, color=black, line_spacing=65)
    # 下划线单独设置
    for p in cell.paragraphs:
        for run in p.runs:
            if run.text == FIXED_ID:
                run.font.underline = True
    # 第4行:拼音 10pt 加粗 灰色(多音字校正后的拼音)
    add_styled_paragraph(cell, FIXED_PINYIN, 10, bold=True, color=gray, line_spacing=20)
    # 第5行:姓名 30pt 加粗 灰色
    add_styled_paragraph(cell, FIXED_NAME, 30, bold=True, color=gray, line_spacing=35)
    # 第6行:有效期 8pt 加粗 灰色
    add_styled_paragraph(cell, date, 8, bold=True, color=gray, line_spacing=15)


# ========== 竖版长方形内容写入(提取自长方形竖版原始完整逻辑,插入图片+底层行间距Oxml)==========
def write_vertical_card(cell, campus, class_name, photo_img_path):
    """
    写入竖版长方形内容(54×85mm,提取自 create_vertical_documents 原始完整逻辑)
    竖版布局:单列显示所有信息
    顺序:园区 → 照片占位图片 → 班级+学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)
    font_name = "微软雅黑"
    line_spacing_multiplier = 1

    # 添加学校园区信息
    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} {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

    # 插入照片占位图片 2.5cm*3.5cm
    p_photo = cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pr = p_photo._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)
    try:
        run_pic = p_photo.add_run()
        run_pic.add_picture(photo_img_path, width=Cm(2.5), height=Cm(3.5))
    except Exception as e:
        print(f"竖版插入照片失败:{e}")
        run_text = p_photo.add_run("[贴照片]")
        run_text.font.name = font_name
        run_text._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
        run_text.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)

    # 班级+学号
    run1 = p_info.add_run(f"{class_name}_")
    run1.font.name = font_name
    run1._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run1.font.size = Pt(20)
    run1.font.bold = True
    run2 = p_info.add_run(f"{FIXED_ID}\n")
    run2.font.name = font_name
    run2._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run2.font.size = Pt(20)
    run2.font.bold = True

    # 拼音(多音字校正)
    run_py = p_info.add_run(f"{FIXED_PINYIN}\n")
    run_py.font.name = font_name
    run_py._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_py.font.size = Pt(12)
    run_py.font.bold = False

    # 姓名
    run_name = p_info.add_run(f"{FIXED_NAME}\n")
    run_name.font.name = font_name
    run_name._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_name.font.size = Pt(25)
    run_name.font.bold = True

    # 有效期
    run_date = p_info.add_run(f"有效期:{date}")
    run_date.font.name = font_name
    run_date._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_date.font.size = Pt(10)
    run_date.font.bold = False


# ========== 横版长方形内容写入(提取自长方形横版原始完整逻辑,嵌套表格插入图片,底层Oxml行间距)==========
def write_horizontal_card(left_cell, right_cell, campus, class_name, photo_img_path):
    """
    横版长方形(无嵌套表格)
    left_cell:左单元格 放照片+园区+日期
    right_cell:右单元格 放班级、拼音、姓名、学号
    """
    font_name = "微软雅黑"
    line_spacing_multiplier = 1

    # ========== 左单元格:照片 + 园区 + 有效期 ==========
    left_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(left_cell)

    p_photo = left_cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pr = p_photo._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)
    try:
        run_pic = p_photo.add_run()
        run_pic.add_picture(photo_img_path, width=Cm(2.5), height=Cm(3.5))
    except Exception as e:
        print(f"横版左栏插入照片失败:{e}")
        run_text = p_photo.add_run("[贴照片]")
        run_text.font.name = font_name
        run_text._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
        run_text.font.size = Pt(10)

    p_info_left = left_cell.add_paragraph()
    p_info_left.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pr = p_info_left._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_campus = p_info_left.add_run(f"{school} {campus}\n")
    run_campus.font.name = font_name
    run_campus._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_campus.font.size = Pt(14)
    run_campus.font.bold = False

    run_date_left = p_info_left.add_run(f"有效期:{date}")
    run_date_left.font.name = font_name
    run_date_left._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_date_left.font.size = Pt(10)
    run_date_left.font.bold = False


    # ========== 右单元格:班级 + 拼音 + 姓名 + 学号 ==========
    right_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(right_cell)

    p_double = right_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_class = p_double.add_run(f"{class_name}\n")
    run_class.font.name = font_name
    run_class._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_class.font.size = Pt(20)
    run_class.font.bold = True

    run_py = p_double.add_run(f"{FIXED_PINYIN}\n")
    run_py.font.name = font_name
    run_py._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_py.font.size = Pt(12)
    run_py.font.bold = False

    run_name = p_double.add_run(f"{FIXED_NAME}\n")
    run_name.font.name = font_name
    run_name._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_name.font.size = Pt(25)
    run_name.font.bold = True

    run_id = p_double.add_run(f"{FIXED_ID}")
    run_id.font.name = font_name
    run_id._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_id.font.size = Pt(20)
    run_id.font.bold = True

# ========== 从Excel读取班级列表 ==========
def get_classes_from_excel(file_path):
    """从Excel读取所有班级的(园区, 班级名),去重"""
    import pandas as pd
    classes = []
    seen = set()
    xls = pd.ExcelFile(file_path)
    for sheet_name in xls.sheet_names:
        df = pd.read_excel(file_path, sheet_name=sheet_name, header=None, dtype=str)
        # 取第2行(索引1)的园区和班级
        if len(df) > 1:
            campus = str(df.iloc[1, 1]).strip() if pd.notna(df.iloc[1, 1]) else ''
            class_name = str(df.iloc[1, 2]).strip() if pd.notna(df.iloc[1, 2]) else sheet_name
            key = (campus, class_name)
            if campus and class_name and key not in seen:
                classes.append(key)
                seen.add(key)
    return classes


# ========== 为单个班级生成一页文档 ==========
# ========== 为单个班级生成一页文档 ==========
def generate_class_page(campus, class_name, output_path, photo_img_path):
    """
    基于模板为单个班级生成一页牌子文档
    填入5个牌子位置:3圆牌 + 1竖版 + 1横版
    """
    # 复制模板
    shutil.copy2(template_file, output_path)
    doc = Document(output_path)

    if len(doc.tables) == 0:
        print(f"  警告: 模板中没有找到表格")
        return False

    table = doc.tables[0]

    # ===== 旧合并逻辑已经注释,不再使用cell_h变量 =====
    # try:
    #     cell_h = table.cell(4, 0).merge(table.cell(4, 1)).merge(table.cell(4, 2))
    #     set_cell_right_border(cell_h)
    # except Exception as e:
    #     print(f"  合并横版单元格时出错: {e}")
    #     cell_h = table.cell(4, 0)
    #     set_cell_right_border(cell_h)

    # ===== 使用第4行0、1原生单元格做横版左右栏,无嵌套表格 =====
    cell_h_left = table.cell(4, 0)   # 左列:照片、园区
    cell_h_right = table.cell(4, 1)  # 右列:班级姓名学号
    set_cell_right_border(cell_h_left)
    set_cell_right_border(cell_h_right)
    print(f"  填入 横版长方形(第4行 0/1单元格)")
    write_horizontal_card(cell_h_left, cell_h_right, campus, class_name, photo_img_path)


    # ===== 填入其他牌子内容 =====
    # 圆牌1:第1行左(列0-2已合并)→ cell(1,0)
    print(f"  填入 圆牌1(第1行左)")
    write_circle_card(table.cell(1, 0), campus, class_name)

    # 圆牌2:第1行右(列3-4已合并)→ cell(1,3)
    print(f"  填入 圆牌2(第1行右)")
    write_circle_card(table.cell(1, 3), campus, class_name)

    # 圆牌3:第2行左(列0-2已合并)→ cell(2,0)
    print(f"  填入 圆牌3(第2行左)")
    write_circle_card(table.cell(2, 0), campus, class_name)

    # 竖版长方形:第2行列3 → cell(2,3),尺寸5.4cm宽×8.5cm高
    print(f"  填入 竖版长方形(第2行列3)")
    write_vertical_card(table.cell(2, 3), campus, class_name, photo_img_path)

    # ========= 删除!!!删掉下面这行旧代码,这就是报错根源 =========
    # print(f"  填入 横版长方形(第4行列0-2)")
    # write_horizontal_card(cell_h, campus, class_name, photo_img_path)

    # 保存
    doc.save(output_path)
    print(f"  已生成: {os.path.basename(output_path)}")
    return True


# # ========== 清理临时文件 ==========
# def cleanup(temp_path):
#     try:
#         if os.path.exists(temp_path):
#             shutil.rmtree(temp_path)
#             print(f"已清理临时文件夹: {temp_path}")
#     except Exception as e:
#         print(f"清理临时文件夹失败: {e}")


# ========== 主程序 ==========
if __name__ == "__main__":
    print("=" * 60)
    print("班级牌子样例生成器【支持多音字/复姓拼音校正】")
    print("每页包含:3个圆牌 + 1个竖版长方形 + 1个横版长方形")
    print(f"统一内容:{FIXED_NAME} {FIXED_ID} {FIXED_PINYIN}")
    print("=" * 60)

    # 第一步:生成照片占位图片
    create_photo_placeholder(photo_path)

    # 步骤1:获取班级列表
    if TARGET_CLASSES:
        classes = TARGET_CLASSES
        print(f"\n使用指定班级列表(共{len(classes)}个):")
    else:
        if os.path.exists(excel_file):
            classes = get_classes_from_excel(excel_file)
            print(f"\n从Excel读取到 {len(classes)} 个班级:")
        else:
            print(f"错误: 找不到Excel文件 {excel_file}")
            exit(1)

    for campus, class_name in classes:
        print(f"  - {campus} {class_name}")

    # 步骤2:为每个班级生成单页文档
    print(f"\n{'=' * 60}")
    print("开始生成每个班级的单页文档...")
    print("=" * 60)

    temp_files = []
    for idx, (campus, class_name) in enumerate(classes, 1):
        print(f"\n[{idx}/{len(classes)}] 处理: {campus} {class_name}")
        temp_file = os.path.join(temp_dir, f"04_{dates}_{class_name}_插班生专用_圆牌长方牌打印.docx")
        success = generate_class_page(campus, class_name, temp_file, photo_path)
        if success:
            temp_files.append(temp_file)

    # 步骤4:清理临时文件(如需开启取消下面注释)
    # cleanup(temp_dir)

    # 步骤5:输出运行时间
    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}秒)")
    print(f"共生成 {len(classes)} 个班级的牌子样例页")

完善了说明文字,替换内容(姓名、学号、有效期、拼音可改可不改,后续可以替换园区,年级)

五、感悟

1.AI编程技术通过读取附件(word单元格、Excel数据、Python代码)来理解人的设计意图,快速生成代码。解决困扰我很久的“插班生名牌”浪费纸张的问题。

2.本次最费劲的还是“word单元格+文本框图形”的设计、勾选等问题。所以一定要把word上传资源库备用,以免遗失!!!

3.虽然做了“00 幼儿园班级名单张三.xlsx”表,但是实际读取的还是“01 20260805班级信息表.xlsx”

4.有了这套插班生单独打印,圆形挂牌就不用特地生成1份和2份。

开学初教师统一打印1份全班孩子数据(如果需要被子牌、接送卡、床贴,就打印3套),

零散的插班生,只要单独用今天这套“圆形+长方形组合word”,教师通过全选批量替换,快速把5张挂牌的名字信息都替换掉,避免人工一个个修改,导致格式字体消失错误等问题。

我发现横版里面一条中线黑线,之前做3*3横版没有这个问题

# -*- coding: utf-8 -*-
'''班级牌子样例生成器基于5行5列Word模板,每页包含:
  - 3个圆牌(第1行左、第1行右、第2行左)
  - 1个竖版长方形(第2行列3,54×85mm)
  - 1个横版长方形(第4行列0‑1,85×54mm,不嵌套表格,原生双单元格,关闭中间分隔线)
每个班级一页,内容统一:张三,23号,zhāng sān
提取已有代码中圆牌、横版、竖版的内容写入逻辑重新组合
增加多音字、复姓拼音校正功能
Python 3.7.8 兼容 豆包、阿夏20260821
不使用嵌(横版卡片中间没有黑色中线)
豆包、阿夏
20260821
'''
import os
import time
import shutil
from PIL import Image, ImageDraw, ImageFont
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from pypinyin import pinyin, Style

# ========== 记录程序开始时间 ==========
start_time = time.time()

# ========== 创建照片占位符图片 ==========
def create_photo_placeholder(image_path):
    """创建照片占位符图片:2.5cm宽*3.5cm高,黑色框线,中间写"贴照片"""
    try:
        # 确保目录存在
        os.makedirs(os.path.dirname(image_path), exist_ok=True)
        # 设置图片尺寸 (2.5cm x 3.5cm) 转换为300dpi的像素
        width, height = int(2.5 / 2.54 * 300), int(3.5 / 2.54 * 300)
        # 创建白色背景图片
        img = Image.new('RGB', (width, height), 'white')
        draw = ImageDraw.Draw(img)
        # 绘制黑色边框
        draw.rectangle([(10, 10), (width - 10, height - 10)], outline='black', width=2)
        # 添加文字
        try:
            # 尝试使用微软雅黑字体
            font = ImageFont.truetype("msyh.ttc", 20)
        except:
            # 如果找不到字体,使用默认字体
            font = ImageFont.load_default()
        text = "贴照片"
        text_bbox = draw.textbbox((0, 0), text, font=font)
        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, fill='black', font=font)
        # 保存图片
        img.save(image_path)
        print(f"已创建照片占位符: {image_path}")
        return True
    except Exception as e:
        print(f"创建照片占位符失败: {str(e)}")
        return False

# ========== 多音字、复姓拼音校正(从原始长方形卡片代码复制) ==========
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 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)])

# ========== 核心配置 ==========
date = '2026.9-2027.6'
school = 'XXX幼'
dates = '202609'
# 固定内容(所有牌子统一)
FIXED_NAME = '张三'
FIXED_ID = '23号'
FIXED_PINYIN = correct_surname_pinyin(FIXED_NAME)   # 【多音字函数动态生成拼音】
# 路径配置(Windows环境请修改为实际路径)
base_dir = r'D:\Python最终内容\20260902插班生圆牌长方牌'
excel_file = os.path.join(base_dir, "01 20260805班级信息表.xlsx")
template_file = os.path.join(base_dir, "02 插班生专用(圆形+长方形)挂牌模版_无框线.docx")
temp_dir = os.path.join(base_dir, "03_班级牌子样例_word可编辑")
photo_path = os.path.join(base_dir, "photo_placeholder.png")

os.makedirs(temp_dir, exist_ok=True)
# 要生成的班级列表(留空则从Excel读取所有班级)
# 格式:[(园区, 班级名), ...]
# 例如:[('总园', '中1班'), ('总园', '小2班')]
TARGET_CLASSES = []  # 留空=全部班级

# ========== 边框工具函数 新增:设置单元格指定方向边框 ==========
def set_cell_one_side_border(cell, side_tag, val="single", sz="4", color="000000"):
    """
    side_tag示例:"w:right" / "w:left" / "w:top" / "w:bottom"
    val="none" 代表关闭该侧边框
    """
    tc_pr = cell._element.get_or_add_tcPr()
    tc_borders = tc_pr.find(qn("w:tcBorders"))
    if tc_borders is None:
        tc_borders = OxmlElement("w:tcBorders")
        tc_pr.append(tc_borders)
    old_node = tc_borders.find(qn(side_tag))
    if old_node is not None:
        tc_borders.remove(old_node)
    br = OxmlElement(side_tag)
    br.set(qn("w:val"), val)
    br.set(qn("w:sz"), sz)
    br.set(qn("w:color"), color)
    br.set(qn("w:space"), "0")
    tc_borders.append(br)

# ========== 【修复函数:强制设置单元格右边框黑色实线,解决merge丢失右边框bug】==========
def set_cell_right_border(cell):
    """给单元格设置黑色实线右边框;修复merge合并单元格后右侧框线消失问题"""
    set_cell_one_side_border(cell, "w:right", val="single", sz="4", color="000000")

# ========== 字体样式工具函数(提取自已有代码)==========
def set_run_font(run, text, font_size, bold=False, color=None, font_name='微软雅黑', underline=False):
    """设置run的字体样式(提取自圆形挂牌代码)"""
    run.text = text
    run.font.name = font_name
    run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run.font.size = Pt(font_size)
    run.font.bold = bold
    run.font.underline = underline
    if color:
        run.font.color.rgb = RGBColor(*color)

def add_styled_paragraph(cell, text, font_size, bold=False, color=None,
                        alignment=WD_PARAGRAPH_ALIGNMENT.CENTER,
                        line_spacing=None, space_before=0, space_after=0):
    """在单元格中添加带样式的段落"""
    p = cell.add_paragraph()
    p.alignment = alignment
    if line_spacing:
        p.paragraph_format.line_spacing = Pt(line_spacing)
    p.paragraph_format.space_before = Pt(space_before)
    p.paragraph_format.space_after = Pt(space_after)
    run = p.add_run()
    set_run_font(run, text, font_size, bold, color)
    return p

def clear_cell(cell):
    """清空单元格内容"""
    cell._element.clear_content()

# ========== 圆牌内容写入(提取自圆形挂牌代码)==========
def write_circle_card(cell, campus, class_name):
    """
    写入圆牌内容(提取自 02圆形挂牌模版 代码的 set_paragraph_style 逻辑)
    内容顺序:园区 → 班级 → 学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)

    gray = (169, 169, 169)   # DarkGray
    black = (0, 0, 0)
    # 第1行:园区 16pt 加粗 灰色
    add_styled_paragraph(cell, f"{school}-{campus}", 16, bold=True, color=gray, line_spacing=45)
    # 第2行:班级 30pt 加粗 灰色
    add_styled_paragraph(cell, class_name, 30, bold=True, color=gray, line_spacing=35)
    # 第3行:学号 50pt 加粗 黑色 下划线
    add_styled_paragraph(cell, FIXED_ID, 50, bold=True, color=black, line_spacing=65)
    # 下划线单独设置
    for p in cell.paragraphs:
        for run in p.runs:
            if run.text == FIXED_ID:
                run.font.underline = True
    # 第4行:拼音 10pt 加粗 灰色(多音字校正后的拼音)
    add_styled_paragraph(cell, FIXED_PINYIN, 10, bold=True, color=gray, line_spacing=20)
    # 第5行:姓名 30pt 加粗 灰色
    add_styled_paragraph(cell, FIXED_NAME, 30, bold=True, color=gray, line_spacing=35)
    # 第6行:有效期 8pt 加粗 灰色
    add_styled_paragraph(cell, date, 8, bold=True, color=gray, line_spacing=15)

# ========== 竖版长方形内容写入(提取自长方形竖版原始完整逻辑,插入图片+底层行间距Oxml)==========
def write_vertical_card(cell, campus, class_name, photo_img_path):
    """
    写入竖版长方形内容(54×85mm,提取自 create_vertical_documents 原始完整逻辑)
    竖版布局:单列显示所有信息
    顺序:园区 → 照片占位图片 → 班级+学号 → 拼音 → 姓名 → 有效期
    """
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(cell)
    font_name = "微软雅黑"
    line_spacing_multiplier = 1
    # 添加学校园区信息
    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} {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
    # 插入照片占位图片 2.5cm*3.5cm
    p_photo = cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pr = p_photo._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)
    try:
        run_pic = p_photo.add_run()
        run_pic.add_picture(photo_img_path, width=Cm(2.5), height=Cm(3.5))
    except Exception as e:
        print(f"竖版插入照片失败:{e}")
        run_text = p_photo.add_run("[贴照片]")
        run_text.font.name = font_name
        run_text._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
        run_text.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)
    # 班级+学号
    run1 = p_info.add_run(f"{class_name}_")
    run1.font.name = font_name
    run1._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run1.font.size = Pt(20)
    run1.font.bold = True
    run2 = p_info.add_run(f"{FIXED_ID}\n")
    run2.font.name = font_name
    run2._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run2.font.size = Pt(20)
    run2.font.bold = True
    # 拼音(多音字校正)
    run_py = p_info.add_run(f"{FIXED_PINYIN}\n")
    run_py.font.name = font_name
    run_py._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_py.font.size = Pt(12)
    run_py.font.bold = False
    # 姓名
    run_name = p_info.add_run(f"{FIXED_NAME}\n")
    run_name.font.name = font_name
    run_name._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_name.font.size = Pt(25)
    run_name.font.bold = True
    # 有效期
    run_date = p_info.add_run(f"有效期:{date}")
    run_date.font.name = font_name
    run_date._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_date.font.size = Pt(10)
    run_date.font.bold = False

# ========== 横版长方形内容写入【无嵌套表格,原生两个单元格】==========
def write_horizontal_card(left_cell, right_cell, campus, class_name, photo_img_path):
    """
    横版长方形(无嵌套表格,原生两个单元格)
    left_cell:左单元格 放照片+园区+日期
    right_cell:右单元格 放班级、拼音、姓名、学号
    """
    font_name = "微软雅黑"
    line_spacing_multiplier = 1
    # ========== 左单元格:照片 + 园区 + 有效期 ==========
    left_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(left_cell)

    p_photo = left_cell.add_paragraph()
    p_photo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pr = p_photo._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)
    try:
        run_pic = p_photo.add_run()
        run_pic.add_picture(photo_img_path, width=Cm(2.5), height=Cm(3.5))
    except Exception as e:
        print(f"横版左栏插入照片失败:{e}")
        run_text = p_photo.add_run("[贴照片]")
        run_text.font.name = font_name
        run_text._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
        run_text.font.size = Pt(10)

    p_info_left = left_cell.add_paragraph()
    p_info_left.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
    p_pr = p_info_left._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_campus = p_info_left.add_run(f"{school} {campus}\n")
    run_campus.font.name = font_name
    run_campus._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_campus.font.size = Pt(14)
    run_campus.font.bold = False

    run_date_left = p_info_left.add_run(f"有效期:{date}")
    run_date_left.font.name = font_name
    run_date_left._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_date_left.font.size = Pt(10)
    run_date_left.font.bold = False

    # ========== 右单元格:班级 + 拼音 + 姓名 + 学号 ==========
    right_cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    clear_cell(right_cell)

    p_double = right_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_class = p_double.add_run(f"{class_name}\n")
    run_class.font.name = font_name
    run_class._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_class.font.size = Pt(20)
    run_class.font.bold = True

    run_py = p_double.add_run(f"{FIXED_PINYIN}\n")
    run_py.font.name = font_name
    run_py._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_py.font.size = Pt(12)
    run_py.font.bold = False

    run_name = p_double.add_run(f"{FIXED_NAME}\n")
    run_name.font.name = font_name
    run_name._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_name.font.size = Pt(25)
    run_name.font.bold = True

    run_id = p_double.add_run(f"{FIXED_ID}")
    run_id.font.name = font_name
    run_id._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
    run_id.font.size = Pt(20)
    run_id.font.bold = True

# ========== 从Excel读取班级列表 ==========
def get_classes_from_excel(file_path):
    """从Excel读取所有班级的(园区, 班级名),去重"""
    import pandas as pd
    classes = []
    seen = set()
    xls = pd.ExcelFile(file_path)
    for sheet_name in xls.sheet_names:
        df = pd.read_excel(file_path, sheet_name=sheet_name, header=None, dtype=str)
        # 取第2行(索引1)的园区和班级
        if len(df) > 1:
            campus = str(df.iloc[1, 1]).strip() if pd.notna(df.iloc[1, 1]) else ''
            class_name = str(df.iloc[1, 2]).strip() if pd.notna(df.iloc[1, 2]) else sheet_name
            key = (campus, class_name)
            if campus and class_name and key not in seen:
                classes.append(key)
                seen.add(key)
    return classes

# ========== 为单个班级生成一页文档 ==========
def generate_class_page(campus, class_name, output_path, photo_img_path):
    """
    基于模板为单个班级生成一页牌子文档
    填入5个牌子位置:3圆牌 + 1竖版 + 1横版
    不做单元格合并,第4行0、1原生单元格做横版;代码关闭中间分隔竖线
    """
    # 复制模板
    shutil.copy2(template_file, output_path)
    doc = Document(output_path)

    if len(doc.tables) == 0:
        print(f"  警告: 模板中没有找到表格")
        return False
    table = doc.tables[0]

    # ===== 不合并单元格,使用第4行0、1原生单元格做横版左右栏 =====
    cell_h_left = table.cell(4, 0)   # 左列:照片、园区
    cell_h_right = table.cell(4, 1)  # 右列:班级姓名学号

    # 写入横版卡片内容
    write_horizontal_card(cell_h_left, cell_h_right, campus, class_name, photo_img_path)

    # ----------------【核心!!代码强制关闭中间分隔黑线】----------------
    # 左单元格右边框 设置为 none;右单元格左边框 设置为 none,消除中间分割线
    set_cell_one_side_border(cell_h_left, "w:right", val="none", sz="0")
    set_cell_one_side_border(cell_h_right, "w:left", val="none", sz="0")
    # 保留横版整块最右侧黑色实线外框
    set_cell_right_border(cell_h_right)
    # -------------------------------------------------------------------

    print(f"  填入 横版长方形(第4行 0/1单元格,代码关闭中间竖线)")

    # ===== 填入其他牌子内容 =====
    # 圆牌1:第1行左(列0‑2已合并)→ cell(1,0)
    print(f"  填入 圆牌1(第1行左)")
    write_circle_card(table.cell(1, 0), campus, class_name)

    # 圆牌2:第1行右(列3‑4已合并)→ cell(1,3)
    print(f"  填入 圆牌2(第1行右)")
    write_circle_card(table.cell(1, 3), campus, class_name)

    # 圆牌3:第2行左(列0‑2已合并)→ cell(2,0)
    print(f"  填入 圆牌3(第2行左)")
    write_circle_card(table.cell(2, 0), campus, class_name)

    # 竖版长方形:第2行列3 → cell(2,3),尺寸5.4cm宽×8.5cm高
    print(f"  填入 竖版长方形(第2行列3)")
    write_vertical_card(table.cell(2, 3), campus, class_name, photo_img_path)

    # 保存
    doc.save(output_path)
    print(f"  已生成: {os.path.basename(output_path)}")
    return True

# ========== 主程序 ==========
if __name__ == "__main__":
    print("=" * 60)
    print("班级牌子样例生成器【支持多音字/复姓拼音校正|无嵌套表格版本】")
    print("每页包含:3个圆牌 + 1个竖版长方形 + 1个横版长方形")
    print(f"统一内容:{FIXED_NAME} {FIXED_ID} {FIXED_PINYIN}")
    print("=" * 60)

    # 第一步:生成照片占位图片
    create_photo_placeholder(photo_path)

    # 步骤1:获取班级列表
    if TARGET_CLASSES:
        classes = TARGET_CLASSES
        print(f"\n使用指定班级列表(共{len(classes)}个):")
    else:
        if os.path.exists(excel_file):
            classes = get_classes_from_excel(excel_file)
            print(f"\n从Excel读取到 {len(classes)} 个班级:")
        else:
            print(f"错误: 找不到Excel文件 {excel_file}")
            exit(1)

    for campus, class_name in classes:
        print(f"  - {campus} {class_name}")

    # 步骤2:为每个班级生成单页文档
    print(f"\n{'=' * 60}")
    print("开始生成每个班级的单页文档...")
    print("=" * 60)

    temp_files = []
    for idx, (campus, class_name) in enumerate(classes, 1):
        print(f"\n[{idx}/{len(classes)}] 处理: {campus} {class_name}")
        temp_file = os.path.join(temp_dir, f"04_{dates}_{class_name}_插班生专用_圆牌长方牌打印.docx")
        success = generate_class_page(campus, class_name, temp_file, photo_path)
        if success:
            temp_files.append(temp_file)

    # 步骤5:输出运行时间
    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}秒)")
    print(f"共生成 {len(classes)} 个班级的牌子样例页")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿夏reasonsummer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值