Python自动办公工具-设置Word文档中表格的格式

简介: 本项目适合需要快速标准化表格样式或实现条件格式化的场景

一:效果展示:

本项目是基于PyQt5python-docx库开发的图形化应用程序,用于对Word文档中的表格进行格式化设置。用户可以通过直观的界面调整表格的各种格式属性,包括基本格式、条件格式、边框和背景等
屏幕截图 2025-11-28 202831.png

屏幕截图 2025-11-28 202855.png

屏幕截图 2025-11-28 202919.png

二:功能描述:

1. 核心功能

(1)文件操作模块
  • 打开文件:允许用户选择并加载.docx文件,自动检测文件中的第一个表格
  • 保存文件:将修改后的文档保存为新文件(在原文件名后添加"-整理"后缀)
  • 文件状态显示:在界面顶部显示当前打开的文件名
(2)基本格式设置
  1. 行高设置:
  • 默认行高:设置表格数据行的默认高度(单位:磅)
  • 表头行数:指定表格中表头的行数(表头行高固定为1cm)
  1. 对齐方式:
  • 水平对齐:居中/左对齐/右对齐
  • 垂直对齐:居中/顶部/底部
  1. 字体设置:
  • 字体名称:下拉选择常见中英文字体(包括系统默认字体)
  • 字体大小:8-72磅可调
  • 字体样式:加粗/斜体复选框
  • 字体颜色:通过颜色选择器设置
(3)条件格式设置
  1. 条件1设置:
  • 应用列:指定要应用条件的列号
  • 操作符:>=, >, =, <=, <, <>
  • 比较值:数值比较
  • 背景色:满足条件时单元格的背景颜色
  1. 条件2设置:
  • 应用列:指定要应用条件的列号
  • 操作符:>=, >, =, <=, <, <>
  • 比较值:支持数值和文本比较
  • 背景色:满足条件时单元格的背景颜色
(4)边框和背景设置
  1. 边框设置:
  • 边框样式:单线/虚线/点线/双线/无线条
  • 边框宽度:1-24磅可调
  • 边框颜色:通过颜色选择器设置
  • 应用边框:可单独选择应用左边框、上边框、右边框或下边框
  1. 背景设置:
  • 背景颜色:通过颜色选择器设置
  • 应用范围:所有单元格/表头/数据行/特定列
  • 列选择:当选择 "特定列" 时指定列号

2. 功能实现细节

(1)表格格式化核心类 - DocxTableEditor
  • set_cell_border:使用python-docx的底层XML操作设置单元格边框样式
  • set_background_color:通过XML操作设置单元格背景颜色
  • set_cell_font:设置单元格内文本的字体属性(名称、大小、加粗、斜体、颜色)
(2)条件格式实现
  • 支持数值比较(条件1)和文本比较(条件2)
  • 只有当用户选择了背景颜色时才应用条件格式
  • 自动跳过表头行(根据表头行数设置)
(3)用户界面特性
  • 使用选项卡组织不同功能模块
  • 颜色选择后实时预览(标签显示颜色并设置背景色)
  • 智能字体选择(包含常见中英文字体并尝试检测系统默认字体)
  • 详细的错误处理和用户反馈(通过QMessageBox

3. 使用流程

(1)打开文档:

点击"打开文件"按钮选择包含表格的Word文档

(2)设置格式
  • 在"基本格式"选项卡中设置表格的基础样式
  • 在"条件格式"选项卡中设置基于单元格值的条件格式
  • 在"边框和背景"选项卡中设置边框样式和单元格背景
(3)保存文档

点击"保存文件"按钮将修改保存为新文档

4. 技术特点

  1. 跨平台性:基于PyQt5开发,可在WindowsmacOSLinux上运行
  2. 非破坏性编辑:保留原始文档内容,只修改指定格式属性
  3. 直观的用户界面:通过图形化控件简化复杂的表格格式设置

5. 适用场景

  1. 批量处理报表文档的格式
  2. 标准化不同来源的Word表格样式
  3. 通过条件格式可突出显示特定数据

三:完整代码:

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QPushButton, QFileDialog, QGroupBox, QGridLayout,
                             QCheckBox, QComboBox, QLineEdit, QSpinBox, QColorDialog,
                             QMessageBox, QTabWidget, QFormLayout)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.oxml import OxmlElement
from docx.oxml.ns import qn, nsdecls
from docx.oxml import parse_xml

class DocxTableEditor:
    @staticmethod
    def set_cell_border(cell, **kwargs):
        tc = cell._tc
        tcPr = tc.get_or_add_tcPr()

        tcBorders = tcPr.first_child_found_in("w:tcBorders")
        if tcBorders is None:
            tcBorders = OxmlElement('w:tcBorders')
            tcPr.append(tcBorders)

        for edge in ('start', 'top', 'end', 'bottom', 'insideH', 'insideV'):
            edge_data = kwargs.get(edge)
            if edge_data:
                tag = 'w:{}'.format(edge)

                element = tcBorders.find(qn(tag))
                if element is None:
                    element = OxmlElement(tag)
                    tcBorders.append(element)

                for key in ["sz", "val", "color", "space", "shadow"]:
                    if key in edge_data:
                        element.set(qn('w:{}'.format(key)), str(edge_data[key]))

    @staticmethod
    def set_background_color(cell, rgb_color):
        if isinstance(rgb_color, QColor):
            rgb_color = rgb_color.name()[1:]  
        elif isinstance(rgb_color, str) and rgb_color.startswith('#'):
            rgb_color = rgb_color[1:]
        rgb_color = f"{rgb_color:0>6}"

        shading_elm = parse_xml(r'<w:shd {} w:fill="{color_value}"/>'.format(nsdecls('w'), color_value=rgb_color))
        cell._tc.get_or_add_tcPr().append(shading_elm)

    @staticmethod
    def set_cell_font(cell, font_name=None, font_size=None, bold=None, italic=None, color=None):
        for paragraph in cell.paragraphs:
            for run in paragraph.runs:
                if font_name:
                    run.font.name = font_name
                if font_size:
                    run.font.size = Pt(font_size)
                if bold is not None:
                    run.font.bold = bold
                if italic is not None:
                    run.font.italic = italic
                if color:
                    if isinstance(color, QColor):
                        run.font.color.rgb = RGBColor(color.red(), color.green(), color.blue())
                    elif isinstance(color, str):
                        if color.startswith('#'):
                            color = color[1:]
                        try:
                            r = int(color[0:2], 16)
                            g = int(color[2:4], 16)
                            b = int(color[4:6], 16)
                            run.font.color.rgb = RGBColor(r, g, b)
                        except (ValueError, IndexError):
                            run.font.color.rgb = RGBColor(0, 0, 0)
                    else:
                        run.font.color.rgb = RGBColor(0, 0, 0)

class TableFormatterApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("设置Word文档表格格式")
        self.setGeometry(100, 100, 800, 600)
        self.doc = None
        self.table = None
        self.file_path = ""
        self.font_color_label = QLabel("未选择")
        self.cond1_color_label = QLabel("未选择")
        self.cond2_color_label = QLabel("未选择")
        self.border_color_label = QLabel("未选择")
        self.bg_color_label = QLabel("未选择")
        self.init_ui()

    def init_ui(self):
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        file_group = QGroupBox("文件操作")
        file_layout = QHBoxLayout()
        self.file_label = QLabel("未选择文件")
        self.file_label.setAlignment(Qt.AlignCenter)
        open_btn = QPushButton("打开文件")
        open_btn.clicked.connect(self.open_file)
        save_btn = QPushButton("保存文件")
        save_btn.clicked.connect(self.save_file)
        file_layout.addWidget(self.file_label)
        file_layout.addWidget(open_btn)
        file_layout.addWidget(save_btn)
        file_group.setLayout(file_layout)
        tab_widget = QTabWidget()
        basic_tab = QWidget()
        basic_layout = QVBoxLayout()
        row_group = QGroupBox("行高设置")
        row_form = QFormLayout()
        self.row_height_spin = QSpinBox()
        self.row_height_spin.setRange(10, 100)
        self.row_height_spin.setValue(30)
        self.row_height_spin.setSuffix(" 磅")
        self.header_row_spin = QSpinBox()
        self.header_row_spin.setRange(0, 10)
        self.header_row_spin.setValue(1)
        row_form.addRow("默认行高:", self.row_height_spin)
        row_form.addRow("表头行数:", self.header_row_spin)
        row_group.setLayout(row_form)
        align_group = QGroupBox("对齐方式")
        align_layout = QHBoxLayout()
        self.align_combo = QComboBox()
        self.align_combo.addItems(["居中", "左对齐", "右对齐"])
        self.valign_combo = QComboBox()
        self.valign_combo.addItems(["居中", "顶部", "底部"])
        align_layout.addWidget(QLabel("水平对齐:"))
        align_layout.addWidget(self.align_combo)
        align_layout.addWidget(QLabel("垂直对齐:"))
        align_layout.addWidget(self.valign_combo)
        align_group.setLayout(align_layout)
        font_group = QGroupBox("字体设置")
        font_layout = QFormLayout()
        self.font_name_combo = QComboBox()
        common_fonts = [
            "默认字体",  
            "宋体", "黑体", "楷体", "仿宋", "微软雅黑", "Arial", "Times New Roman",
            "Calibri", "Courier New", "Verdana", "Tahoma", "Helvetica"
        ]
        self.font_name_combo.addItems(common_fonts)

        try:
            import matplotlib.font_manager as fm
            default_font = fm.FontProperties().get_name()
            if default_font not in common_fonts:
                self.font_name_combo.insertItem(1, default_font)
        except:
            pass

        self.font_size_spin = QSpinBox()
        self.font_size_spin.setRange(8, 72)
        self.font_size_spin.setValue(11)
        self.bold_check = QCheckBox("加粗")
        self.italic_check = QCheckBox("斜体")
        font_color_layout = QHBoxLayout()
        self.font_color_btn = QPushButton("字体颜色")
        self.font_color_btn.clicked.connect(self.choose_font_color)
        font_color_layout.addWidget(self.font_color_btn)
        font_color_layout.addWidget(self.font_color_label)
        font_layout.addRow("字体名称:", self.font_name_combo)
        font_layout.addRow("字体大小:", self.font_size_spin)
        font_layout.addRow(self.bold_check)
        font_layout.addRow(self.italic_check)
        font_layout.addRow("字体颜色:", font_color_layout)
        font_group.setLayout(font_layout)
        apply_basic_btn = QPushButton("应用基本格式")
        apply_basic_btn.clicked.connect(self.apply_basic_formatting)
        basic_layout.addWidget(row_group)
        basic_layout.addWidget(align_group)
        basic_layout.addWidget(font_group)
        basic_layout.addWidget(apply_basic_btn)
        basic_tab.setLayout(basic_layout)
        cond_tab = QWidget()
        cond_layout = QVBoxLayout()
        cond_group = QGroupBox("条件格式设置")
        cond_grid = QGridLayout()
        cond_grid.addWidget(QLabel("条件1:"), 0, 0)
        self.cond1_col_spin = QSpinBox()
        self.cond1_col_spin.setRange(1, 20)
        self.cond1_col_spin.setValue(6)  # 默认第6列(数量列)
        cond_grid.addWidget(QLabel("列号:"), 0, 1)
        cond_grid.addWidget(self.cond1_col_spin, 0, 2)
        self.cond1_op_combo = QComboBox()
        self.cond1_op_combo.addItems([">=", ">", "=", "<=", "<", "<>"])
        cond_grid.addWidget(QLabel("操作符:"), 0, 3)
        cond_grid.addWidget(self.cond1_op_combo, 0, 4)
        self.cond1_value_spin = QSpinBox()
        self.cond1_value_spin.setRange(0, 10000)
        self.cond1_value_spin.setValue(85)
        cond_grid.addWidget(QLabel("值:"), 0, 5)
        cond_grid.addWidget(self.cond1_value_spin, 0, 6)
        cond1_color_layout = QHBoxLayout()
        self.cond1_color_btn = QPushButton("背景色")
        self.cond1_color_btn.clicked.connect(lambda: self.choose_condition_color(1))
        cond1_color_layout.addWidget(self.cond1_color_btn)
        cond1_color_layout.addWidget(self.cond1_color_label)
        cond_grid.addLayout(cond1_color_layout, 0, 7)
        cond_grid.addWidget(QLabel("条件2:"), 1, 0)
        self.cond2_col_spin = QSpinBox()
        self.cond2_col_spin.setRange(1, 20)
        cond_grid.addWidget(QLabel("列号:"), 1, 1)
        cond_grid.addWidget(self.cond2_col_spin, 1, 2)
        self.cond2_op_combo = QComboBox()
        self.cond2_op_combo.addItems([">=", ">", "=", "<=", "<", "<>"])
        cond_grid.addWidget(QLabel("操作符:"), 1, 3)
        cond_grid.addWidget(self.cond2_op_combo, 1, 4)
        self.cond2_value_edit = QLineEdit()
        self.cond2_value_edit.setPlaceholderText("数值或文本")
        cond_grid.addWidget(QLabel("值:"), 1, 5)
        cond_grid.addWidget(self.cond2_value_edit, 1, 6)
        cond2_color_layout = QHBoxLayout()
        self.cond2_color_btn = QPushButton("背景色")
        self.cond2_color_btn.clicked.connect(lambda: self.choose_condition_color(2))
        cond2_color_layout.addWidget(self.cond2_color_btn)
        cond2_color_layout.addWidget(self.cond2_color_label)
        cond_grid.addLayout(cond2_color_layout, 1, 7)
        cond_group.setLayout(cond_grid)
        apply_cond_btn = QPushButton("应用条件格式")
        apply_cond_btn.clicked.connect(self.apply_condition_formatting)
        cond_layout.addWidget(cond_group)
        cond_layout.addWidget(apply_cond_btn)
        cond_tab.setLayout(cond_layout)
        border_tab = QWidget()
        border_layout = QVBoxLayout()
        border_group = QGroupBox("边框设置")
        border_form = QFormLayout()
        self.border_style_combo = QComboBox()
        self.border_style_combo.addItems(["single", "dashed", "dotted", "double", "none"])
        self.border_style_combo.setCurrentText("single")
        self.border_width_spin = QSpinBox()
        self.border_width_spin.setRange(1, 24)
        self.border_width_spin.setValue(4)
        self.border_width_spin.setSuffix(" 磅")
        border_color_layout = QHBoxLayout()
        self.border_color_btn = QPushButton("边框颜色")
        self.border_color_btn.clicked.connect(self.choose_border_color)
        border_color_layout.addWidget(self.border_color_btn)
        border_color_layout.addWidget(self.border_color_label)
        self.border_sides_check = []
        side_layout = QHBoxLayout()
        sides = ["左边框", "上边框", "右边框", "下边框"]
        for side in sides:
            check = QCheckBox(side)
            check.setChecked(True)
            self.border_sides_check.append(check)
            side_layout.addWidget(check)

        border_form.addRow("边框样式:", self.border_style_combo)
        border_form.addRow("边框宽度:", self.border_width_spin)
        border_form.addRow("边框颜色:", border_color_layout)
        border_form.addRow("应用边框:", side_layout)
        border_group.setLayout(border_form)
        bg_group = QGroupBox("背景设置")
        bg_layout = QHBoxLayout()
        bg_color_layout = QHBoxLayout()
        self.bg_color_btn = QPushButton("选择背景色")
        self.bg_color_btn.clicked.connect(self.choose_bg_color)
        bg_color_layout.addWidget(self.bg_color_btn)
        bg_color_layout.addWidget(self.bg_color_label)
        self.bg_range_combo = QComboBox()
        self.bg_range_combo.addItems(["所有单元格", "表头", "数据行", "特定列"])
        self.bg_col_spin = QSpinBox()
        self.bg_col_spin.setRange(1, 20)
        self.bg_col_spin.setValue(1)
        bg_layout.addLayout(bg_color_layout)
        bg_layout.addWidget(QLabel("应用范围:"))
        bg_layout.addWidget(self.bg_range_combo)
        bg_layout.addWidget(QLabel("列号:"))
        bg_layout.addWidget(self.bg_col_spin)
        bg_group.setLayout(bg_layout)
        apply_border_btn = QPushButton("应用边框和背景")
        apply_border_btn.clicked.connect(self.apply_border_and_background)
        border_layout.addWidget(border_group)
        border_layout.addWidget(bg_group)
        border_layout.addWidget(apply_border_btn)
        border_tab.setLayout(border_layout)
        tab_widget.addTab(basic_tab, "基本格式")
        tab_widget.addTab(cond_tab, "条件格式")
        tab_widget.addTab(border_tab, "边框和背景")
        main_layout.addWidget(file_group)
        main_layout.addWidget(tab_widget)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

    def open_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "打开Word文件", "", "Word文件 (*.docx)")
        if file_path:
            self.file_path = file_path
            self.file_label.setText(file_path.split("/")[-1])
            try:
                self.doc = Document(file_path)
                if len(self.doc.tables) > 0:
                    self.table = self.doc.tables[0]
                    QMessageBox.information(self, "成功", "文件加载成功!")
                else:
                    QMessageBox.warning(self, "警告", "文件中没有表格!")
                    self.table = None
            except Exception as e:
                QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
                self.table = None

    def save_file(self):
        if not self.doc:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        if not self.file_path:
            save_path, _ = QFileDialog.getSaveFileName(self, "保存Word文件", "", "Word文件 (*.docx)")
            if not save_path:
                return
        else:
            save_path = self.file_path.replace(".docx", "-整理.docx")

        try:
            self.doc.save(save_path)
            QMessageBox.information(self, "成功", f"文件已保存到:\n{save_path}")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存文件失败:\n{str(e)}")

    def choose_font_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.font_color = color
            self.font_color_label.setText(color.name())
            self.font_color_label.setStyleSheet(f"background-color: {color.name()}; color: white;")

    def choose_condition_color(self, cond_num):
        color = QColorDialog.getColor()
        if color.isValid():
            if cond_num == 1:
                self.cond1_color = color.name()[1:]
                self.cond1_color_label.setText(color.name())
                self.cond1_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")
            else:
                self.cond2_color = color.name()[1:]
                self.cond2_color_label.setText(color.name())
                self.cond2_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")

    def choose_border_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.border_color = color.name()[1:]
            self.border_color_label.setText(color.name())
            self.border_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")

    def choose_bg_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.bg_color = color.name()[1:]
            self.bg_color_label.setText(color.name())
            self.bg_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")

    def apply_basic_formatting(self):
        if not self.table:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        try:
            align_map = {
   "居中": WD_ALIGN_PARAGRAPH.CENTER, "左对齐": WD_ALIGN_PARAGRAPH.LEFT,
                         "右对齐": WD_ALIGN_PARAGRAPH.RIGHT}
            valign_map = {
   "居中": WD_ALIGN_VERTICAL.CENTER, "顶部": WD_ALIGN_VERTICAL.TOP, "底部": WD_ALIGN_VERTICAL.BOTTOM}

            align = align_map[self.align_combo.currentText()]
            valign = valign_map[self.valign_combo.currentText()]
            font_name = self.font_name_combo.currentText() if self.font_name_combo.currentText() != "默认字体" else None
            font_size = self.font_size_spin.value() if self.font_size_spin.value() > 0 else None
            bold = self.bold_check.isChecked()
            italic = self.italic_check.isChecked()
            header_rows = self.header_row_spin.value()
            default_row_height = self.row_height_spin.value()

            for i, row in enumerate(self.table.rows):
                if i < header_rows:
                    row.height = Cm(1)  
                else:
                    row.height = Pt(default_row_height)

                for cell in row.cells:
                    for paragraph in cell.paragraphs:
                        paragraph.alignment = align
                    cell.vertical_alignment = valign
                    DocxTableEditor.set_cell_font(
                        cell,
                        font_name=font_name,
                        font_size=font_size,
                        bold=bold,
                        italic=italic,
                        color=self.font_color  
                    )

            QMessageBox.information(self, "成功", "基本格式应用成功!")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"应用基本格式时出错:\n{str(e)}")

    def apply_condition_formatting(self):
        if not self.table:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        try:
            cond1_col = self.cond1_col_spin.value() - 1
            cond1_op = self.cond1_op_combo.currentText()
            cond1_value = self.cond1_value_spin.value()
            cond1_color = self.cond1_color if hasattr(self, 'cond1_color') else None
            cond2_col = self.cond2_col_spin.value() - 1
            cond2_op = self.cond2_op_combo.currentText()
            cond2_value = self.cond2_value_edit.text()
            cond2_color = self.cond2_color if hasattr(self, 'cond2_color') else None

            for row in self.table.rows[self.header_row_spin.value():]:
                try:
                    cell_text = row.cells[cond1_col].text.strip()
                    if cell_text and cond1_color: 
                        try:
                            cell_value = float(cell_text)
                            condition_met = self.evaluate_condition(cell_value, cond1_op, cond1_value)

                            if condition_met:
                                DocxTableEditor.set_background_color(row.cells[cond1_col], cond1_color)
                        except ValueError:
                            pass

                    if cond2_value and cond2_color:  
                        cell_text = row.cells[cond2_col].text.strip()
                        try:
                            cell_value = float(cell_text)
                            cond2_value_float = float(cond2_value)
                            condition_met = self.evaluate_condition(cell_value, cond2_op, cond2_value_float)
                        except ValueError:
                            condition_met = self.evaluate_text_condition(cell_text, cond2_op, cond2_value)

                        if condition_met:
                            DocxTableEditor.set_background_color(row.cells[cond2_col], cond2_color)

                except (ValueError, IndexError):
                    continue

            QMessageBox.information(self, "成功", "条件格式应用成功!")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"应用条件格式时出错:\n{str(e)}")

    def evaluate_condition(self, value, op, compare_value):
        if op == ">=":
            return value >= compare_value
        elif op == ">":
            return value > compare_value
        elif op == "=":
            return value == compare_value
        elif op == "<=":
            return value <= compare_value
        elif op == "<":
            return value < compare_value
        elif op == "<>":
            return value != compare_value
        return False

    def evaluate_text_condition(self, text, op, compare_text):
        if op == "=":
            return text == compare_text
        elif op == "<>":
            return text != compare_text
        return False

    def apply_border_and_background(self):
        if not self.table:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        try:
            border_style = self.border_style_combo.currentText()
            border_width = self.border_width_spin.value()
            border_color = self.border_color

            sides = []
            if self.border_sides_check[0].isChecked(): sides.append('start')
            if self.border_sides_check[1].isChecked(): sides.append('top')
            if self.border_sides_check[2].isChecked(): sides.append('end')
            if self.border_sides_check[3].isChecked(): sides.append('bottom')

            bg_range = self.bg_range_combo.currentText()
            bg_col = self.bg_col_spin.value() - 1  
            header_rows = self.header_row_spin.value()

            for i, row in enumerate(self.table.rows):
                for j, cell in enumerate(row.cells):
                    if sides:
                        border_settings = {
   }
                        for side in sides:
                            border_settings[side] = {
   
                                "val": border_style,
                                "sz": border_width,
                                "color": border_color
                            }
                        DocxTableEditor.set_cell_border(cell, **border_settings)

                    if self.bg_color:
                        apply_bg = False

                        if bg_range == "所有单元格":
                            apply_bg = True
                        elif bg_range == "表头" and i < header_rows:
                            apply_bg = True
                        elif bg_range == "数据行" and i >= header_rows:
                            apply_bg = True
                        elif bg_range == "特定列" and j == bg_col:
                            apply_bg = True

                        if apply_bg:
                            DocxTableEditor.set_background_color(cell, self.bg_color)

            QMessageBox.information(self, "成功", "边框和背景应用成功!")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"应用边框和背景时出错:\n{str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = TableFormatterApp()
    window.show()
    sys.exit(app.exec_())

四:代码分析:

1. DocxTableEditor 工具类

class DocxTableEditor:
    """用于操作Word表格样式的工具类"""

    @staticmethod
    def set_cell_border(cell, **kwargs):
        """设置单元格边框样式

        Args:
            cell: 表格单元格对象
            **kwargs: 边框参数,如 start/top/end/bottom/insideH/insideV 等边的样式
                     每边参数格式: {'val': 'single', 'sz': 4, 'color': 'FF0000'}
        """
        tc = cell._tc
        tcPr = tc.get_or_add_tcPr()
        tcBorders = tcPr.first_child_found_in("w:tcBorders")

        # 如果不存在边框定义则创建
        if tcBorders is None:
            tcBorders = OxmlElement('w:tcBorders')
            tcPr.append(tcBorders)

        # 遍历所有指定的边
        for edge in ('start', 'top', 'end', 'bottom', 'insideH', 'insideV'):
            edge_data = kwargs.get(edge)
            if edge_data:
                tag = 'w:{}'.format(edge)
                element = tcBorders.find(qn(tag))

                # 如果不存在该边的定义则创建
                if element is None:
                    element = OxmlElement(tag)
                    tcBorders.append(element)

                # 设置边框属性
                for key in ["sz", "val", "color", "space", "shadow"]:
                    if key in edge_data:
                        element.set(qn('w:{}'.format(key)), str(edge_data[key]))

    @staticmethod
    def set_background_color(cell, rgb_color):
        """设置单元格背景色

        Args:
            cell: 表格单元格对象
            rgb_color: 颜色值,可以是QColor对象或十六进制字符串(带#或不带)
        """
        # 统一颜色格式为6位十六进制字符串
        if isinstance(rgb_color, QColor):
            rgb_color = rgb_color.name()[1:]  # 去掉QColor返回的#前缀
        elif isinstance(rgb_color, str) and rgb_color.startswith('#'):
            rgb_color = rgb_color[1:]
        rgb_color = f"{rgb_color:0>6}"  # 确保6位长度

        # 创建shading元素并设置填充色
        shading_elm = parse_xml(r'<w:shd {} w:fill="{color_value}"/>'.format(
            nsdecls('w'), color_value=rgb_color))
        cell._tc.get_or_add_tcPr().append(shading_elm)

    @staticmethod
    def set_cell_font(cell, font_name=None, font_size=None, bold=None, italic=None, color=None):
        """设置单元格内文字样式

        Args:
            cell: 表格单元格对象
            font_name: 字体名称
            font_size: 字体大小(pt)
            bold: 是否加粗
            italic: 是否斜体
            color: 字体颜色(QColor/十六进制字符串)
        """
        for paragraph in cell.paragraphs:
            for run in paragraph.runs:
                if font_name:
                    run.font.name = font_name
                if font_size:
                    run.font.size = Pt(font_size)
                if bold is not None:
                    run.font.bold = bold
                if italic is not None:
                    run.font.italic = italic
                if color:
                    # 处理不同颜色输入格式
                    if isinstance(color, QColor):
                        run.font.color.rgb = RGBColor(color.red(), color.green(), color.blue())
                    elif isinstance(color, str):
                        if color.startswith('#'):
                            color = color[1:]
                        try:
                            r = int(color[0:2], 16)
                            g = int(color[2:4], 16)
                            b = int(color[4:6], 16)
                            run.font.color.rgb = RGBColor(r, g, b)
                        except (ValueError, IndexError):
                            run.font.color.rgb = RGBColor(0, 0, 0)
                    else:
                        run.font.color.rgb = RGBColor(0, 0, 0)

2. 主窗口初始化 (TableFormatterApp 类)

class TableFormatterApp(QMainWindow):
    """主应用程序窗口"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("设置Word文档表格格式")
        self.setGeometry(100, 100, 800, 600)
        self.doc = None  # 当前打开的Document对象
        self.table = None  # 当前操作的表格对象
        self.file_path = ""  # 当前文件路径

        # 初始化颜色标签(用于显示当前选择的颜色)
        self.font_color_label = QLabel("未选择")
        self.cond1_color_label = QLabel("未选择")
        self.cond2_color_label = QLabel("未选择")
        self.border_color_label = QLabel("未选择")
        self.bg_color_label = QLabel("未选择")

        self.init_ui()  # 初始化界面

    def init_ui(self):
        """初始化用户界面"""
        main_widget = QWidget()
        main_layout = QVBoxLayout()

        # ========== 文件操作区域 ==========
        file_group = QGroupBox("文件操作")
        file_layout = QHBoxLayout()
        self.file_label = QLabel("未选择文件")
        self.file_label.setAlignment(Qt.AlignCenter)

        open_btn = QPushButton("打开文件")
        open_btn.clicked.connect(self.open_file)

        save_btn = QPushButton("保存文件")
        save_btn.clicked.connect(self.save_file)

        file_layout.addWidget(self.file_label)
        file_layout.addWidget(open_btn)
        file_layout.addWidget(save_btn)
        file_group.setLayout(file_layout)

        # ========== 选项卡区域 ==========
        tab_widget = QTabWidget()

        # 基本格式选项卡
        basic_tab = self._create_basic_tab()
        # 条件格式选项卡
        cond_tab = self._create_cond_tab()
        # 边框和背景选项卡
        border_tab = self._create_border_tab()

        tab_widget.addTab(basic_tab, "基本格式")
        tab_widget.addTab(cond_tab, "条件格式")
        tab_widget.addTab(border_tab, "边框和背景")

        # ========== 主布局组装 ==========
        main_layout.addWidget(file_group)
        main_layout.addWidget(tab_widget)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

3. 核心功能方法

def open_file(self):
    """打开Word文件并加载第一个表格"""
    file_path, _ = QFileDialog.getOpenFileName(self, "打开Word文件", "", "Word文件 (*.docx)")
    if file_path:
        self.file_path = file_path
        self.file_label.setText(file_path.split("/")[-1])
        try:
            self.doc = Document(file_path)
            if len(self.doc.tables) > 0:
                self.table = self.doc.tables[0]
                QMessageBox.information(self, "成功", "文件加载成功!")
            else:
                QMessageBox.warning(self, "警告", "文件中没有表格!")
                self.table = None
        except Exception as e:
            QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
            self.table = None

def save_file(self):
    """保存修改后的Word文件"""
    if not self.doc:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    if not self.file_path:
        save_path, _ = QFileDialog.getSaveFileName(self, "保存Word文件", "", "Word文件 (*.docx)")
        if not save_path:
            return
    else:
        save_path = self.file_path.replace(".docx", "-整理.docx")

    try:
        self.doc.save(save_path)
        QMessageBox.information(self, "成功", f"文件已保存到:\n{save_path}")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"保存文件失败:\n{str(e)}")

def apply_basic_formatting(self):
    """应用基本格式设置到表格"""
    if not self.table:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    try:
        # 获取对齐方式设置
        align_map = {
   "居中": WD_ALIGN_PARAGRAPH.CENTER, 
                    "左对齐": WD_ALIGN_PARAGRAPH.LEFT,
                    "右对齐": WD_ALIGN_PARAGRAPH.RIGHT}
        valign_map = {
   "居中": WD_ALIGN_VERTICAL.CENTER, 
                     "顶部": WD_ALIGN_VERTICAL.TOP, 
                     "底部": WD_ALIGN_VERTICAL.BOTTOM}

        align = align_map[self.align_combo.currentText()]
        valign = valign_map[self.valign_combo.currentText()]

        # 获取字体设置
        font_name = self.font_name_combo.currentText() if self.font_name_combo.currentText() != "默认字体" else None
        font_size = self.font_size_spin.value() if self.font_size_spin.value() > 0 else None
        bold = self.bold_check.isChecked()
        italic = self.italic_check.isChecked()
        header_rows = self.header_row_spin.value()
        default_row_height = self.row_height_spin.value()

        # 遍历表格应用设置
        for i, row in enumerate(self.table.rows):
            # 设置行高(表头行固定1cm,其他行使用指定磅值)
            if i < header_rows:
                row.height = Cm(1)
            else:
                row.height = Pt(default_row_height)

            # 设置单元格格式
            for cell in row.cells:
                # 设置段落对齐
                for paragraph in cell.paragraphs:
                    paragraph.alignment = align

                # 设置垂直对齐
                cell.vertical_alignment = valign

                # 设置字体
                DocxTableEditor.set_cell_font(
                    cell,
                    font_name=font_name,
                    font_size=font_size,
                    bold=bold,
                    italic=italic,
                    color=getattr(self, 'font_color', None)  # 使用getattr避免未定义属性错误
                )

        QMessageBox.information(self, "成功", "基本格式应用成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"应用基本格式时出错:\n{str(e)}")

def apply_condition_formatting(self):
    """应用条件格式到表格"""
    if not self.table:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    try:
        # 获取条件1设置
        cond1_col = self.cond1_col_spin.value() - 1  # 转换为0-based索引
        cond1_op = self.cond1_op_combo.currentText()
        cond1_value = self.cond1_value_spin.value()
        cond1_color = getattr(self, 'cond1_color', None)

        # 获取条件2设置
        cond2_col = self.cond2_col_spin.value() - 1
        cond2_op = self.cond2_op_combo.currentText()
        cond2_value = self.cond2_value_edit.text()
        cond2_color = getattr(self, 'cond2_color', None)

        # 跳过表头行
        for row in self.table.rows[self.header_row_spin.value():]:
            try:
                # 处理条件1(数值比较)
                cell_text = row.cells[cond1_col].text.strip()
                if cell_text and cond1_color:
                    try:
                        cell_value = float(cell_text)
                        if self.evaluate_condition(cell_value, cond1_op, cond1_value):
                            DocxTableEditor.set_background_color(row.cells[cond1_col], cond1_color)
                    except ValueError:
                        pass  # 非数值内容跳过

                # 处理条件2(支持文本比较)
                if cond2_value and cond2_color:
                    cell_text = row.cells[cond2_col].text.strip()
                    try:
                        # 尝试数值比较
                        cell_value = float(cell_text)
                        cond2_value_float = float(cond2_value)
                        condition_met = self.evaluate_condition(cell_value, cond2_op, cond2_value_float)
                    except ValueError:
                        # 回退到文本比较
                        condition_met = self.evaluate_text_condition(cell_text, cond2_op, cond2_value)

                    if condition_met:
                        DocxTableEditor.set_background_color(row.cells[cond2_col], cond2_color)

            except (ValueError, IndexError):
                continue  # 跳过格式错误的单元格

        QMessageBox.information(self, "成功", "条件格式应用成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"应用条件格式时出错:\n{str(e)}")

def evaluate_condition(self, value, op, compare_value):
    """数值条件判断"""
    if op == ">=":
        return value >= compare_value
    elif op == ">":
        return value > compare_value
    elif op == "=":
        return value == compare_value
    elif op == "<=":
        return value <= compare_value
    elif op == "<":
        return value < compare_value
    elif op == "<>":
        return value != compare_value
    return False

def evaluate_text_condition(self, text, op, compare_text):
    """文本条件判断"""
    if op == "=":
        return text == compare_text
    elif op == "<>":
        return text != compare_text
    return False

def apply_border_and_background(self):
    """应用边框和背景设置"""
    if not self.table:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    try:
        # 获取边框设置
        border_style = self.border_style_combo.currentText()
        border_width = self.border_width_spin.value()
        border_color = getattr(self, 'border_color', None)

        # 获取要设置边框的边
        sides = []
        for idx, check in enumerate(self.border_sides_check):
            if check.isChecked():
                side_map = ['start', 'top', 'end', 'bottom']
                sides.append(side_map[idx])

        # 获取背景设置
        bg_range = self.bg_range_combo.currentText()
        bg_col = self.bg_col_spin.value() - 1
        header_rows = self.header_row_spin.value()

        # 遍历表格应用设置
        for i, row in enumerate(self.table.rows):
            for j, cell in enumerate(row.cells):
                # 应用边框
                if sides and border_color:
                    border_settings = {
   }
                    for side in sides:
                        border_settings[side] = {
   
                            "val": border_style,
                            "sz": border_width,
                            "color": border_color
                        }
                    DocxTableEditor.set_cell_border(cell, **border_settings)

                # 应用背景色
                if hasattr(self, 'bg_color') and self.bg_color:
                    apply_bg = False

                    # 判断是否在指定范围内
                    if bg_range == "所有单元格":
                        apply_bg = True
                    elif bg_range == "表头" and i < header_rows:
                        apply_bg = True
                    elif bg_range == "数据行" and i >= header_rows:
                        apply_bg = True
                    elif bg_range == "特定列" and j == bg_col:
                        apply_bg = True

                    if apply_bg:
                        DocxTableEditor.set_background_color(cell, self.bg_color)

        QMessageBox.information(self, "成功", "边框和背景应用成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"应用边框和背景时出错:\n{str(e)}")
目录
相关文章
|
3月前
|
UED Python
Python自动办公工具01-Excel文件编辑器
本项目适合需要快速查看和修改Excel文件内容的场景,适合不需要完整Excel功能但又要比纯文本编辑更直观的用户使用
122 2
|
3月前
|
索引 Python
Python自动办公工具05-Word表中相同内容的单元格自动合并
本项目适合处理Word 文档(.docx)中的表格合并需求,用于检测指定列中内容相同的相邻单元格,并合并为一个单元格
247 2
|
API Python
Python-Docx库 | Word与Python的完美结合(附使用文档)
Python-Docx库 | Word与Python的完美结合(附使用文档)
3674 0
|
4月前
|
安全 网络安全 数据安全/隐私保护
2026年OpenClaw(Clawdbot)服务器安全配置指南:从部署到加固步骤
OpenClaw(原Clawdbot、Moltbot)作为高权限AI自动化工具,其私有化部署特性意味着服务器的安全直接关系到数据隐私与系统稳定。2026年,随着AI Agent技术的普及,针对云服务器的暴力破解、权限滥用、端口扫描等攻击手段愈发频繁。阿里云作为国内领先的云服务平台,提供了多层次的安全防护机制,结合OpenClaw的特性进行针对性加固,能有效抵御各类安全风险。
1984 4
|
安全 Go 索引
Go切片循环就用range 有这一篇就够了
Go切片循环就用range 有这一篇就够了
1087 0
|
3月前
|
人工智能 搜索推荐 算法
深度解析:Geo优化中Json-LD的生效周期与高质量构建全指南
在生成式引擎优化(GEO)的浪潮中,结构化数据已不再是SEO的“选修课”,而是AI理解网页灵魂的“必修课”。
182 7
|
3月前
|
存储 Java 中间件
分布式协调双雄深度拆解:ZooKeeper 与 Nacos 从底层原理到生产实战全指南
本文深度解析ZooKeeper与Nacos两大分布式协调中间件:ZooKeeper专注强一致协调,基于ZAB协议与ZNode模型,适用于大数据生态;Nacos则提供AP/CP双模、三层数据隔离及长轮询机制,是云原生下配置中心+服务发现的一站式选择。二者核心能力、架构差异与选型建议全面对比,附生产实践与避坑指南。
1268 6
|
3月前
|
存储 NoSQL Java
扛住百万级 QPS:高并发架构核心三板斧全解
本文系统阐述高并发架构三大核心支柱:流量削峰(前端拦截、网关限流、应用缓冲、分布式限流)、异步化(本地CompletableFuture与RocketMQ分布式解耦)及水平扩展(无状态化、服务注册发现、读写分离与分库分表),并以秒杀系统为例实战整合,兼顾避坑指南与概念辨析。
499 3
|
3月前
|
存储 人工智能 数据库
保姆级:2026年阿里云优惠券指南领取、使用、查询与云服务器购买省钱技巧
2026年阿里云优惠券保姆级指南:免费领取AI焕新季1728元代金券、学生300元无门槛券、企业上云补贴、域名口令等;详解领券入口(AI焕新季/活动中心/高校计划/企业补贴)、查询路径(费用中心→卡券)及使用方法,助您云服务器、域名等采购省更多!
566 0
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
AI智能审计系统,企业风控的“超级大脑”
AI智能审计系统正重塑传统审计:通过NLP读懂合同邮件,用机器学习预警风险,实现7×24小时自动化审查。它让审计从“事后找茬”变为“事前防控”,助力企业风险管理迈向智能化。技术人的新战场,来了!(238字)
705 0

热门文章

最新文章