You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

openpyxl插入行后表格下方单元格值与样式丢失问题求助

openpyxl插入行后表格下方单元格值与样式丢失问题求助

我目前在用openpyxl处理xlsx模板文件:加载模板、填充数据后保存,核心需求是保留模板原有的格式(包括单元格样式、颜色、布局等)。其中有个关键操作是往表格里插入列表数据——我原本的计划是先给表格新增足够数量的行,再把数据逐个单元格写入新行,但现在遇到了一个棘手的问题:插入行之后,表格下方的所有单元格值、样式、颜色全部丢失了,只有合并单元格的尺寸、插入的图片还保留,而且表格下方仅剩我在脚本里手动设置的那些值。这个问题在模板的4个带表格的工作表里都会出现。

以下是我简化后的其中一个工作表的填充逻辑代码:

from openpyxl.worksheet.worksheet import Worksheet
from typing import List, Tuple
import copy

# 假设相关的实体类(Product、ImportationProductLink等)已提前定义

def _fill_oferta_cliente(
        self,
        ws: Worksheet,
        table_data: List[Tuple[Product, ImportationProductLink]],
        offer_date,
        importation: Importation,
        containers: List[ContainersValue],
    ) -> None:
        TABLE_START_ROW = 6
        TABLE_START_COL = 3
        TABLE_NUM_COLS = 9
        TABLE_DATA_LEN = len(table_data)
        INSERTED_ROWS = TABLE_DATA_LEN - 1

        original_merges = self._capture_merges(ws)

        # 先解除所有合并单元格
        for merge in list(ws.merged_cells):
            ws.unmerge_cells(str(merge))

        # 为表格插入需要的行
        ws.insert_rows(TABLE_START_ROW + 1, INSERTED_ROWS)

        # 填充表格数据
        for idx, (product, link) in enumerate(table_data):
            target_row = TABLE_START_ROW + idx

            # 从第二行开始复制模板行的格式
            if idx > 0:
                self._copy_row(
                    ws=ws,
                    source_row=TABLE_START_ROW,
                    target_row=target_row,
                    start_col=TABLE_START_COL,
                    num_cols=TABLE_NUM_COLS,
                )
            initial_form_row = 11 + idx
            values = [
                product.tariff_heading,  # Partida Arancelaria
                product.reference,  # Referencia
                product.description,  # Producto / Descripción
                product.unit_of_measure,  # Unidad de medida
                link.units,  # Unidades
                f"='F.PRECIO EXTENDIDA'!J{initial_form_row}",  # PRECIO UNITARIO CIF USD
                f"='F.PRECIO EXTENDIDA'!S{initial_form_row}",  # IMPORTE CIF USD
                f"='F.PRECIO CLIENTES'!J{initial_form_row-1}",  # PRECIO UNITARIO CUP
                f"='F.PRECIO CLIENTES'!O{initial_form_row-1}",  # IMPORTE CUP
            ]

            self._write_row(
                ws=ws,
                row=target_row,
                start_col=TABLE_START_COL,
                values=values,
            )

        # 处理容器网格数据
        SQUARE_SECTION_COLUMNS = 6
        SQUARE_SECTION_START_ROW = 12
        result = len(containers) // SQUARE_SECTION_COLUMNS
        exc = len(containers) % SQUARE_SECTION_COLUMNS
        container_square_rows = result + (1 if exc > 0 else 0)
        ws.insert_rows(SQUARE_SECTION_START_ROW - 1, container_square_rows - 1)
        INSERTED_ROWS = INSERTED_ROWS + container_square_rows - 1

        for i in range(container_square_rows):
            j = 0
            max_j = SQUARE_SECTION_COLUMNS * (i + 1)
            if (i + 1) == container_square_rows and exc > 0:
                max_j = exc
            else:
                break
            while j < max_j:
                container = containers[j]
                ws.cell(row=SQUARE_SECTION_START_ROW + i, column=j + 6).value = container.value
                j = j + 1

        # 设置公式和静态值
        ws[f"I{6+TABLE_DATA_LEN}"] = f"=SUM(I6:I{6+INSERTED_ROWS})"
        ws[f"I{8+TABLE_DATA_LEN}"] = f"=+I{6+TABLE_DATA_LEN}*H{8+TABLE_DATA_LEN}"
        ws[f"K{8+TABLE_DATA_LEN}"] = f"=+K{6+TABLE_DATA_LEN}*H{8+TABLE_DATA_LEN}"
        ws[f"K{20+INSERTED_ROWS}"] = f"=+F.PRECIO!I37*H{9+INSERTED_ROWS}"
        ws[f"K{21+INSERTED_ROWS}"] = f"=+F.PRECIO!I38*H{9+INSERTED_ROWS}"
        ws[f"K{28+INSERTED_ROWS}"] = f"=SUM(K{24+INSERTED_ROWS}:K{27+INSERTED_ROWS})"
        ws[f"F{33+INSERTED_ROWS}"] = importation.client.legal_address
        ws[f"F{34+INSERTED_ROWS}"] = importation.client.user.phone_number
        ws[f"F{35+INSERTED_ROWS}"] = importation.client.user.email
        ws[f"F{36+INSERTED_ROWS}"] = importation.client.establishment_denomination
        ws[f"F{37+INSERTED_ROWS}"] = importation.delivery
        ws[f"F{39+INSERTED_ROWS}"] = importation.client.bank_name
        ws[f"F{40+INSERTED_ROWS}"] = importation.client.branch_office
        ws[f"F{41+INSERTED_ROWS}"] = importation.client.holder
        ws[f"F{42+INSERTED_ROWS}"] = importation.client.account_number
        ws["K2"].value = offer_date

        # 调整并恢复合并单元格
        adjusted_merges = self._adjust_container_merges(
            original_merges,
            container_square_rows,
        )
        self._restore_merges(
            ws=ws,
            merges=adjusted_merges,
            insert_row=7,
            inserted_rows=INSERTED_ROWS,
        )

    def _write_row(self, ws: Worksheet, row: int, start_col: int, values: List) -> None:
        """将值写入指定行的连续列"""
        for offset, value in enumerate(values):
            ws.cell(row=row, column=start_col + offset, value=value)

    def _copy_row(
        self,
        ws: Worksheet,
        source_row: int,
        target_row: int,
        start_col: int,
        num_cols: int,
    ) -> None:
        """复制指定行的样式和值到目标行"""
        for col_offset in range(num_cols):
            col = start_col + col_offset

            source_cell = ws.cell(row=source_row, column=col)
            target_cell = ws.cell(row=target_row, column=col)

            target_cell.value = source_cell.value

            if source_cell.has_style:
                target_cell.font = copy(source_cell.font)
                target_cell.border = copy(source_cell.border)
                target_cell.fill = copy(source_cell.fill)
                target_cell.number_format = source_cell.number_format
                target_cell.alignment = copy(source_cell.alignment)

    def _capture_merges(self, ws: Worksheet):
        """捕获工作表中所有合并单元格,返回格式为(min_row, max_row, min_col, max_col)的元组列表"""
        merges = []
        for mr in ws.merged_cells.ranges:
            merges.append((mr.min_row, mr.max_row, mr.min_col, mr.max_col))
        return merges

    def _restore_merges(
        self,
        ws: Worksheet,
        merges,
        insert_row: int,
        inserted_rows: int,
    ):
        """根据插入行的位置调整合并单元格范围并恢复合并"""
        for min_row, max_row, min_col, max_col in merges:
            # 合并单元格完全在插入点上方:不调整
            if max_row < insert_row:
                new_min_row = min_row
                new_max_row = max_row
            # 合并单元格完全在插入点下方:整体下移
            elif min_row >= insert_row:
                new_min_row = min_row + inserted_rows
                new_max_row = max_row + inserted_rows
            # 合并单元格覆盖插入点:扩展行范围
            else:
                new_min_row = min_row
                new_max_row = max_row + inserted_rows

            ws.merge_cells(
                start_row=new_min_row,
                end_row=new_max_row,
                start_column=min_col,
                end_column=max_col,
            )

    def _adjust_container_merges(
        self,
        merges,
        container_square_rows: int,
    ):
        """调整容器区域的合并单元格行范围"""
        adjusted = []
        for min_row, max_row, min_col, max_col in merges:
            if min_row == 12 and min_col in (3, 5):
                adjusted.append(
                    (
                        min_row,
                        max_row + (container_square_rows - 1),
                        min_col,
                        max_col,
                    )
                )
            else:
                adjusted.append((min_row, max_row, min_col, max_col))
        return adjusted

我已经尝试了先解除所有合并单元格、插入行后再恢复合并的操作,但还是没能解决表格下方内容丢失的问题。想请教各位,有没有遇到过类似的情况?或者能帮我分析下可能是什么原因导致的吗?

火山引擎 最新活动