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)}")
目录
相关文章
|
1天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10149 30
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
13天前
|
人工智能 安全 Linux
【OpenClaw保姆级图文教程】阿里云/本地部署集成模型Ollama/Qwen3.5/百炼 API 步骤流程及避坑指南
2026年,AI代理工具的部署逻辑已从“单一云端依赖”转向“云端+本地双轨模式”。OpenClaw(曾用名Clawdbot)作为开源AI代理框架,既支持对接阿里云百炼等云端免费API,也能通过Ollama部署本地大模型,完美解决两类核心需求:一是担心云端API泄露核心数据的隐私安全诉求;二是频繁调用导致token消耗过高的成本控制需求。
5861 14
|
21天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
22951 119
|
7天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
1760 4

热门文章

最新文章