python使用docx库对在word中的表格合并操作

python使用docx库对在word中的表格合并操作

 在日常办公自动化场景中,Word表格的单元格合并是常见需求。本文基于python-docx库,系统介绍如何实现水平、垂直及复杂区域的单元格合并操作。

 

水平合并单元格

通过merge_cells_horizontally函数实现行内单元格合并:

from docx import Document

def merge_cells_horizontally(table, row: int, start_col: int, end_col: int) -> None:
    """水平合并单元格
    :param table: 表格对象
    :param row: 行索引(从0开始)
    :param start_col: 起始列索引
    :param end_col: 结束列索引
    """
    if start_col >= end_col:
        raise ValueError("结束列必须大于起始列")
    cell = table.cell(row, start_col)
    for col in range(start_col + 1, end_col + 1):
        cell.merge(table.cell(row, col))

垂直合并单元格

通过merge_cells_vertically函数实现列内单元格合并:

def merge_cells_vertically(table, col: int, start_row: int, end_row: int) -> None:
    """垂直合并单元格
    :param table: 表格对象
    :param col: 列索引(从0开始)
    :param start_row: 起始行索引
    :param end_row: 结束行索引
    """
    if start_row >= end_row:
        raise ValueError("结束行必须大于起始行")
    cell = table.cell(start_row, col)
    for row in range(start_row + 1, end_row + 1):
        cell.merge(table.cell(row, col))

完整实现示例

以下代码演示三种合并方式的综合应用:

def create_sample_document(output_path: str) -> None:
    doc = Document()
    doc.add_heading('表格单元格合并演示', level=1)
    
    # 创建5x5表格
    table = doc.add_table(rows=5, cols=5)
    
    # 填充基础数据
    for row_idx in range(5):
        for col_idx in range(5):
            table.cell(row_idx, col_idx).text = f"({row_idx},{col_idx})"
    
    # 水平合并示例
    merge_cells_horizontally(table, 0, 1, 3)
    table.cell(0, 1).text = "水平合并单元格"
    
    # 垂直合并示例
    merge_cells_vertically(table, 4, 1, 3)
    table.cell(1, 4).text = "垂直合并单元格"
    
    # 复杂合并示例
    for row in range(2, 5):
        merge_cells_horizontally(table, row, 0, 2)
    merge_cells_vertically(table, 0, 2, 4)
    table.cell(2, 0).text = "复杂合并"
    
    doc.save(output_path)

 

 

 实战,生成一个报销单

import os
from docx import Document
from docx.shared import Cm, Pt ,Inches
from docx.oxml.shared import OxmlElement, qn
from docx.enum.table import WD_ROW_HEIGHT_RULE
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_ALIGN_VERTICAL


def set_page_format(doc):
    """设置A4纸张,并调整页边距为窄边距,以容纳3个表格"""
    section = doc.sections[0]
    section.page_width = Cm(21.0)          # A4宽度
    section.page_height = Cm(29.7)         # A4高度
    section.left_margin = Cm(1.2)
    section.right_margin = Cm(1.2)
    section.top_margin = Cm(1.2)
    section.bottom_margin = Cm(1.2)

def set_cell_font(cell, text, bold=False, size_pt=12):
    """设置单元格文本及字体(宋体,默认12号)"""
    cell.text = text
    paragraph = cell.paragraphs[0]
    run = paragraph.runs[0] if paragraph.runs else paragraph.add_run(text)
    run.font.name = '宋体'
    run.font.size = Pt(size_pt)
    run.font.bold = bold
    # 垂直居中
    cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
    paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT

def add_reimbursement_table(doc, data):
    """添加一个完整的费用报销单(标题+表格)"""
    # 1. 添加标题“费用报销单”并居中加粗
    title_para = doc.add_paragraph()
    title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = title_para.add_run("\n费用报销单")
    run.font.size = Pt(14)
    run.font.bold = True
    run.font.name = '宋体'

    # 2. 创建表格:6行4列
    table = doc.add_table(rows=6, cols=6)
    table.style = 'Table Grid'          # 带边框的表格样式
    # 可选:统一设置每行最小高度,避免过于拥挤
    for row in table.rows:
        row.height = Cm(0.7)

    # 获取所有单元格的引用 (行索引0~5)
    merge_cells_horizontally(table, 0, 2, 5) # 表格第1行的第3列到第6列合并
    # 第1行:附单据 | 张数
    set_cell_font(table.cell(0, 0), "附单据", bold=False)
    set_cell_font(table.cell(0, 1), f"{data['attach_num']}张")
    table.rows[0].height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST # 行高设置
    table.rows[0].height = Pt(20) # 行高设置


    # 第2行:公司 | {公司名} | 日期 | {日期}
    merge_cells_horizontally(table, 1, 1, 3) # 表格第2行的第2列到第4列合并

    set_cell_font(table.cell(1, 0), "公司", bold=False)
    set_cell_font(table.cell(1, 1), data['company'])
    set_cell_font(table.cell(1, 4), "日期", bold=False)
    set_cell_font(table.cell(1, 5), data['date'])
    table.rows[1].height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST # 行高设置
    table.rows[1].height = Pt(26) # 行高设置

    # 第3行:申请金额(大写) | 大写金额 | 小写金额 | 小写金额值
    merge_cells_horizontally(table, 2, 1, 3) # 表格第2行的第2列到第4列合并
    set_cell_font(table.cell(2, 0), "申请金额\n(大写)", bold=False)
    set_cell_font(table.cell(2, 1), data['amount_cn'])
    set_cell_font(table.cell(2, 4), "小写(¥)", bold=False)
    set_cell_font(table.cell(2, 5), data['amount_num'])
    table.rows[2].height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST # 行高设置
    table.rows[2].height = Pt(40) # 行高设置


    # 第4行:用途说明 + 合并后四列
    set_cell_font(table.cell(3, 0), "经办人", bold=False)
    set_cell_font(table.cell(3, 1), data['handler'])
    # 合并第4行的第3,4,5,6列
    table.cell(3, 2).merge(table.cell(3, 3)).merge(table.cell(3, 4)).merge(table.cell(3, 5))
    table.rows[3].height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST # 行高设置
    table.rows[3].height = Pt(26) # 行高设置

    # 第5行:主管 | (空白) | 合并后四列
    set_cell_font(table.cell(4, 0), "主管", bold=False)
    set_cell_font(table.cell(4, 1), "")
    # 合并第4行的第3,4,5,6列
    table.cell(4, 2).merge(table.cell(4, 3)).merge(table.cell(4, 4)).merge(table.cell(4, 5))
    table.rows[4].height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST # 行高设置
    table.rows[4].height = Pt(26) # 行高设置

    # 第四行 第五行,后四列在行合并 | 添加报销用途
    merge_cells_vertically(table,2,3,4)
    set_cell_font(table.cell(4, 2), "用途说明:\n {}".format(data['purpose'] ), bold=False)


    # 第6行:出纳 | (空白) |出纳 | (空白) | 领款人 | (空白)
    set_cell_font(table.cell(5, 0), "会计", bold=False)
    set_cell_font(table.cell(5, 1), "")      # 留白
    set_cell_font(table.cell(5, 2), "出纳", bold=False)
    set_cell_font(table.cell(5, 3), "")
    set_cell_font(table.cell(5, 4), "领款人", bold=False)
    set_cell_font(table.cell(5, 5), "")
    table.rows[5].height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST # 行高设置
    table.rows[5].height = Pt(26) # 行高设置

    # 表格后添加一个空行(隔开下一个报销单),不要太多,避免溢出页面
    doc.add_paragraph("")

    # 3. 设置第一行的左、上、右边框为灰色
    set_first_row_top_borders(
        table,
        color="#808080",  # 灰色
        size=6,  # 0.7磅
        style="dashed"  # 实线single
    )


# 水平合并单元格
def merge_cells_horizontally(table, row: int, start_col: int, end_col: int) -> None:
    """水平合并单元格
    :param table: 表格对象
    :param row: 行索引(从0开始)
    :param start_col: 起始列索引
    :param end_col: 结束列索引
    """
    if start_col >= end_col:
        raise ValueError("结束列必须大于起始列")
    cell = table.cell(row, start_col)
    for col in range(start_col + 1, end_col + 1):
        cell.merge(table.cell(row, col))

# 垂直合并单元格
def merge_cells_vertically(table, col: int, start_row: int, end_row: int) -> None:
    """垂直合并单元格
    :param table: 表格对象
    :param col: 列索引(从0开始)
    :param start_row: 起始行索引
    :param end_row: 结束行索引
    """
    if start_row >= end_row:
        raise ValueError("结束行必须大于起始行")
    cell = table.cell(start_row, col)
    for row in range(start_row + 1, end_row + 1):
        cell.merge(table.cell(row, col))

# 设置单个单元格的边框颜色
def set_cell_border(cell, **kwargs):
    """
    设置单元格边框颜色和样式

    参数:
        cell: 单元格对象
        **kwargs: 边框属性,如:
            top={"sz": 12, "val": "single", "color": "#FF0000"}
            left={"sz": 12, "val": "single", "color": "#00FF00"}
            bottom={"sz": 12, "val": "single", "color": "#0000FF"}
            right={"sz": 12, "val": "single", "color": "#000000"}
            insideH={"sz": 12, "val": "single", "color": "#CCCCCC"}  # 水平内边框
            insideV={"sz": 12, "val": "single", "color": "#CCCCCC"}  # 垂直内边框

    尺寸单位: 1磅 = 8个单位 (sz=12 表示 1.5磅)
    颜色格式: 十六进制 "#RRGGBB" 或 "auto"
    """
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()

    # 创建边框元素
    tcBorders = OxmlElement('w:tcBorders')

    # 为每个边框方向设置属性
    for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
        edge_data = kwargs.get(edge)
        if edge_data:
            edge_element = OxmlElement(f'w:{edge}')

            # 设置尺寸
            if 'sz' in edge_data:
                edge_element.set(qn('w:sz'), str(edge_data['sz']))

            # 设置线型 (single, double, dotted, dashed, etc.)
            if 'val' in edge_data:
                edge_element.set(qn('w:val'), edge_data['val'])

            # 设置颜色
            if 'color' in edge_data:
                edge_element.set(qn('w:color'), edge_data['color'])

            # 设置阴影 (可选)
            if 'shadow' in edge_data:
                edge_element.set(qn('w:shadow'), edge_data['shadow'])

            # 设置空格 (可选)
            if 'space' in edge_data:
                edge_element.set(qn('w:space'), str(edge_data['space']))

            tcBorders.append(edge_element)

    tcPr.append(tcBorders)

# 设置整个表格的边框颜色
def set_table_border(table, **kwargs):
    """
    设置整个表格所有单元格的边框

    参数:
        table: 表格对象
        **kwargs: 同 set_cell_border

    示例:
        set_table_border(table,
                        top={"sz": 12, "val": "single", "color": "#FF0000"},
                        left={"sz": 12, "val": "single", "color": "#FF0000"},
                        bottom={"sz": 12, "val": "single", "color": "#FF0000"},
                        right={"sz": 12, "val": "single", "color": "#FF0000"},
                        insideH={"sz": 8, "val": "single", "color": "#0000FF"},
                        insideV={"sz": 8, "val": "single", "color": "#0000FF"})
    """
    for row in table.rows:
        for cell in row.cells:
            set_cell_border(cell, **kwargs)

# 设置表格第一行所有单元格的左、上、右边框为指定颜色
def set_first_row_top_borders(table, color="#808080", size=12, style="single"):
    """
    设置表格第一行所有单元格的左、上、右边框为指定颜色

    参数:
        table: 表格对象
        color: 边框颜色(十六进制),默认灰色 "#808080"
        size: 边框粗细(1/8磅),默认12(1.5磅)
        style: 边框样式,如 "single", "dotted", "dashed" 等
    """
    # 获取第一行
    first_row = table.rows[0]

    # 遍历第一行的所有单元格
    for cell in first_row.cells:
        set_cell_border(
            cell,
            top={"sz": size, "val": style, "color": color},  # 顶部边框
            left={"sz": size, "val": style, "color": color},  # 左侧边框
            right={"sz": size, "val": style, "color": color}  # 右侧边框
            # 注意:没有设置 bottom,所以底部边框保持默认
        )


def main():
    # 创建新文档
    doc = Document()
    set_page_format(doc)

    # ------------------------------------------------------------
    # 配置数据:生成3个报销单,每个报销单的变量值不同
    # ------------------------------------------------------------
    reimbursements = [
        {
            "attach_num": "8",  # 附单据张数
            "company": "西安服务公司",
            "date": "2026.4.23",
            "amount_cn": "叁佰柒拾贰元贰角贰分",
            "amount_num": "372.22",
            "handler": "李某某",
            "purpose": "交通"
        },
        {
            "attach_num": "2",
            "company": "西安服务有限公司",
            "date": "2026.4.23",
            "amount_cn": "壹仟叁佰伍拾玖元叁角捌分",
            "amount_num": "1359.38",
            "handler": "李某某",
            "purpose": "餐饮"
        },
        {
            "attach_num": "3",
            "company": "汽车服务有限公司",
            "date": "2026.4.23",
            "amount_cn": "肆佰伍拾陆元整",
            "amount_num": "456.00",
            "handler": "王芳",
            "purpose": "办公用品"
        }
    ]

    # 依次添加3个报销单表格(根据数据列表)
    for idx, data in enumerate(reimbursements):
        add_reimbursement_table(doc, data)

    # 保存生成的Word文件
    output_path = "费用报销单_A4_三张.docx"
    doc.save(output_path)
    print(f"生成成功!文件已保存为:{output_path}")
    print("可直接打开该文件使用Word打印(A4纸张,三个表格竖向排列)。")

if __name__ == "__main__":
    main()

 

 

 

 

 

 

 

 

常见问题处理

  1. 合并后内容丢失:确保在合并前将需要保留的内容写入左上角单元格
  2. 索引越界错误:检查表格的行列索引是否在有效范围内(0到rows-1/cols-1)
  3. 版本兼容性:建议使用python-docx>=0.8.10版本以获得最佳兼容性

 

 

转:https://comate.baidu.com/zh/page/ijpvr1rh6ca

posted @ 2026-04-25 15:51  与f  阅读(110)  评论(0)    收藏  举报