办公自动化案例:生成国家奖学金申请审批表
随着信息化时代的到来,办公自动化工具和技术在日常工作中的应用越来越广泛。作为一名学生,我们时常需要处理一些文书工作,例如申请表格的填写与审批。在本篇博客中,我将分享一个关于生成国家奖学金申请审批表的办公自动化项目案例,重点介绍如何使用Python和Microsoft Word进行自动化操作,生成符合标准的奖学金申请审批表。
1. 项目背景
国家奖学金申请是每个学年中学生们的重要事务之一。学生需要填写详细的申请信息,并且最终提交给各院系进行审批。然而,传统的手工填写和审批过程往往耗费大量的时间和精力,尤其是对于大量学生的申请,审批人员需要逐一处理。为了提升效率,我们可以利用办公自动化工具生成一份规范的国家奖学金申请审批表(模版)。
2. 项目步骤
2.1 安装并导入所需库
首先,我们需要安装以下Python库:
pip install python-docx pywin32
导入所需部分
from docx import Document
from docx.shared import Pt, Cm
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT, WD_LINE_SPACING, WD_UNDERLINE
from docx.oxml import OxmlElement, parse_xml
from docx.oxml.ns import qn
import win32com.client as win32
from pint import UnitRegistry
2.2 设计文档模板
我们需要设计一个标准的国家奖学金申请审批表模板,模板中将包括以下内容:
2.3 申请审批表的页面布局和字体样式
接下来,我们使用python-docx
库编写代码,定制Word文档的页面布局和字体样式,自动生成填写有学生信息的审批表,确保文档符合特定的排版要求。
2.3.1 创建新文档
初始化一个新的Word文档对象 doc = Document()
。
# 创建一个新的文档
doc = Document()
2.3.2 设置页面大小和边距
设置页面大小为A4纸(21.0 cm x 29.7 cm),页边距为1.8厘米(左、右),2.0厘米(上),2.0厘米(下)。
# 获取文档的第一个节(section)
section = doc.sections[0]
# 设置页面大小为A4
section.page_width = Cm(21.0)
section.page_height = Cm(29.7)
# 设置页边距
section.left_margin = Cm(1.8)
section.right_margin = Cm(1.8)
section.top_margin = Cm(2.0)
section.bottom_margin = Cm(2.0)
2.3.3 设置页眉和页脚距边界的距离
页眉距离上边界1.5厘米,页脚距离下边界2.5厘米。
# 设置页眉和页脚距边界的距离
section.header_distance = Cm(1.5) # 设置页眉距边界为1.5厘米
section.footer_distance = Cm(2.5) # 设置页脚距边界为2.5厘米
2.3.4 修改默认样式
获取并修改Normal
样式的字体,设置为西文字体“Times New Roman”和中文字体“宋体”,设置字体大小为五号(10.5磅)。
# 获取文档的默认样式(Normal 样式)
style = doc.styles['Normal']
# 设置字体为宋体(中文)和 Times New Roman(西文),字号为五号(10.5磅)
font = style.font
font.name = 'Times New Roman' # 设置西文字体为 Times New Roman
font.size = Pt(10.5) # 设置字号为五号(10.5 磅)
2.3.5 设置文档网格
通过自定义函数set_document_grid
设置每页显示46行,每行46个字符,行间距15.6磅,字符间距10.5磅。
# 设置文档网格(行网格和字符网格)
def set_document_grid(section, lines_per_page=46, characters_per_line=46, line_spacing=15.6, char_spacing=10.5):
"""
设置页面的行网格和字符网格
"""
# 获取 section 的页面设置(sectPr)
sectPr = section._sectPr
# 创建并配置 w:docGrid 元素
docGrid = OxmlElement('w:docGrid')
# 设置每页行数
docGrid.set(qn('w:count'), str(lines_per_page)) # 每页行数
# 设置字符网格(每行字符数)
docGrid.set(qn('w:charSpacing'), str(int(char_spacing * 20))) # 字符间距(通过磅转换)
# 设置行网格间距
docGrid.set(qn('w:linePitch'), str(int(line_spacing * 20))) # 行距
# 设置网格类型为行网格
docGrid.set(qn('w:type'), 'lines')
# 将 w:docGrid 添加到 section 的页面设置中
sectPr.append(docGrid)
# 调用设置网格的方法,设定每页46行,每行46字符
set_document_grid(section, lines_per_page=46, characters_per_line=46, line_spacing=15.6, char_spacing=10.5)
2.4 申请审批表各结构概述与生成
2.4.1 题目
生成“2023-2024学年国家奖学金申请审批表”标题,并设置居中、黑体字体及字号。
# 添加第一段文本
p1 = doc.add_paragraph("2023-2024学年国家奖学金申请审批表")
run = p1.runs[0]
# 设置字体为黑体
run.font.name = '黑体'
r = run._element
r.rPr.rFonts.set(qn('w:eastAsia'), '黑体') # 设置中文字体为黑体
# 设置字号为小二(即 18 磅)
run.font.size = Pt(18)
# 设置段落居中对齐
p1.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
p1.paragraph_format.space_before = Pt(0)
p1.paragraph_format.space_after = Pt(0)
# 设置单倍行距
p1.paragraph_format.line_spacing = 1.0 # 单倍行距
2.4.2 学校学号
添加“学校:\t学号:”文本,设置制表位对齐,使用宋体字体,并加粗。
# 添加第二段并设置制表位
p2 = doc.add_paragraph()
tab_stops = p2.paragraph_format.tab_stops
# 使用点单位设置制表位,更适用于字符宽度计算
# 每个字符宽度一般约等于12磅,19.58字符、33.75字符和46.58字符的转换计算如下
char_width_pt = 10.5 # 每个字符约为12磅
tab_stops.add_tab_stop(Pt(19.58 * char_width_pt)) # 第一个制表位
tab_stops.add_tab_stop(Pt(33.75 * char_width_pt)) # 第二个制表位
tab_stops.add_tab_stop(Pt(46.58 * char_width_pt)) # 第三个制表位
# 添加文本
run = p2.add_run("学校:\t学号:")
run.font.name = '宋体'
r = run._element
r.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 设置中文字体为宋体
run.font.size = Pt(12) # 小四字号(12 磅)
run.bold = True # 加粗
# 设置段落对齐方式
p2.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
# 设置段前间距为一行(假设小四字号12磅为一行)、段后间距为0、单倍行距
p2.paragraph_format.space_before = Pt(15.6) # 段前间距为一行,小四字号12磅
p2.paragraph_format.space_after = Pt(0) # 段后间距为0
p2.paragraph_format.line_spacing = 1.0 # 单倍行距
2.4.3 表格
在文档中插入一个16行20列的表格,样式为“Table Grid”。
详细表格编辑内容,展开至 2.5 生成表格模版。
# 添加一个表格
table = doc.add_table(rows=16, cols=20)
table.style = 'Table Grid'
2.4.4 制表信息及版本
在文档右下角添加“制表:全国学生资助管理中心 2024版”文本,设置右对齐并调整缩进与行距。
# 在文档中添加一段文本
p3 = doc.add_paragraph("制表:全国学生资助管理中心 2024版")
# 设置段落格式
paragraph_format = p3.paragraph_format
paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT # 居右对齐
paragraph_format.right_indent = Cm(0.37) # 右侧缩进 0.37 厘米
paragraph_format.space_before = Pt(5.25)
paragraph_format.space_after = 0 # 段后间距 0
paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
# 设置字体大小为五号
run = p3.runs[0]
run.font.size = Pt(10.5) # 五号字体大小为 10.5 磅
2.4.5 保存word文档
# 保存文档到指定位置
doc_path = r"D:\MSIPC\Documents\1T5.docx"
# 保存文档
doc.save(doc_path)
2.5 生成表格模版
2.5.1 定义列宽、行高
定义了不同行的高度映射 (row_heights
),通过 range
映射各行的高度(单位为厘米),根据行号匹配高度,并转换为 twips
单位,通过底层 <w:tr>
元素设置。
# 设置第一列宽度为 1.25 厘米
for row in table.rows:
cell = row.cells[0]
cell.width = Cm(1.25)
# 定义行高的映射
row_heights = {
range(0, 5): 1.08, # 前五行高度 1.08 厘米
range(5, 7): 1.1, # 第 6、7 行高度 0.79 厘米
range(7, 12): 0.95, # 第 8-12 行高度 0.95 厘米
range(12, 13): 9.65, # 第 13 行高度 9.65 厘米
range(13, 14): 9.05, # 第 14 行高度 9.05 厘米
range(14, 15): 7.14, # 第 15 行高度 7.14 厘米
range(15, 16): 7.38, # 第 16 行高度 7.38 厘米
}
# 设置每一行的高度
for i, row in enumerate(table.rows):
for key_range, height in row_heights.items():
if i in key_range:
tr = row._tr # 获取底层的 <w:tr> 元素
trPr = tr.get_or_add_trPr() # 获取或创建 <w:trPr> 子元素
trHeight = OxmlElement('w:trHeight')
trHeight.set(qn('w:val'), str(int(height * 567))) # 转换为 twips 单位
trPr.append(trHeight)
2.5.2 合并单元格
分别合并表格的特定行或列的单元格:
- 第一列: 前 5 行、第 6 和第 7 行、第 8 至 12 行分别逐行合并。
- 其余部分: 按范围遍历不同区域,逐列合并各单元格。
# 填充表格内容并合并第一列的前 5 行
for i in range(1, 5):
cell = table.cell(i, 0)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(i - 1, 0))
for i in range(6, 7):
cell = table.cell(i, 0)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(i - 1, 0))
for i in range(8, 12):
cell = table.cell(i, 0)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(i - 1, 0))
for a in range(4):
for b in range(7, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
for a in range(5, 7):
for b in range(3, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
for a in range(7, 12):
for b in range(4, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
for a in range(12, 16):
for b in range(2, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
2.5.3 创建下划线样式函数
def add_text_with_underline(paragraph, texts_with_styles):
"""
为段落添加指定文本及其样式(带或不带下划线)。
:param paragraph: docx 的段落对象
:param texts_with_styles: [(text, underline)] 列表,text 是文本内容,underline 是布尔值,是否带下划线
"""
for text, underline in texts_with_styles:
run = paragraph.add_run(text)
run.font.underline = underline
2.5.4 表格内容填充
table.cell(0, 0).text = '基本情况'
table.cell(0, 1).text = '姓名'
table.cell(0, 3).text = '性别'
table.cell(0, 5).text = '出生年月'
table.cell(1, 1).text = '政治面貌'
table.cell(1, 3).text = '民族'
table.cell(1, 5).text = '入学时间'
table.cell(2, 1).text = '院系'
table.cell(2, 3).text = '专业'
table.cell(2, 5).text = '学制'
table.cell(3, 1).text = '年级'
table.cell(3, 3).text = '班级'
table.cell(3, 5).text = '联系电话'
table.cell(4, 1).text = '身份证号'
table.cell(5, 0).text = '学习情况'
# 第 5 行第 1 列内容
add_text_with_underline(
table.cell(5, 1).paragraphs[0],
[
('成绩排名:', False),
(' / ', True),
('(名次/总人数)', False)
]
)
# 第 5 行第 2 列内容
add_text_with_underline(
table.cell(5, 2).paragraphs[0],
[
('实行综合考评排名:', False),
(' ', True),
('(选填“是”或“否”)', False)
]
)
# 第 6 行第 1 列内容
add_text_with_underline(
table.cell(6, 1).paragraphs[0],
[
('必修课', False),
(' ', True),
('门,其中及格以上', False),
(' ', True),
('门', False)
]
)
# 第 6 行第 2 列内容
add_text_with_underline(
table.cell(6, 2).paragraphs[0],
[
('如是,排名:', False),
(' / ', True),
('(名次/总人数)', False)
]
)
table.cell(7, 0).text = '主要获奖情况'
table.cell(7, 1).text = '日期'
table.cell(7, 2).text = '奖项名称'
table.cell(7, 3).text = '颁奖单位'
table.cell(12, 0).text = '申请理由'
# 定义内容
content = (
"以第三人称从个人简介,思想素质,学习情况,考证情况,工作情况等方面撰写。" +"\n" * 13 +
"申请人签名(手签):\n\n" + "\t\t年\t月\t日"
)
# 获取目标单元格
cell = table.cell(12, 1)
# 按换行符拆分内容
lines = content.split("\n")
# 清空单元格默认内容
cell.text = lines[0]
# 循环设置段落内容
for i in range(1, len(lines)):
# 添加段落
paragraph = cell.add_paragraph(lines[i])
# # 设置段落行间距
paragraph_format = paragraph.paragraph_format
# 设置首行缩进为20字符
if i == len(lines) - 1 or i == len(lines) - 3: # 倒数第一行和倒数第三行
paragraph_format.first_line_indent = Cm(20 * 0.5 / 1.27) # 20字符对应厘米值
else:
paragraph_format.first_line_indent = None # 其他行无首行缩进
table.cell(13, 0).text = '推荐理由'
# 定义内容
content = (
"\n" * 12 + "推荐人(辅导员或班主任)签名:\n" + "\t\t年\t月\t日"
)
# 获取目标单元格
cell = table.cell(13, 1)
# 按换行符拆分内容
lines = content.split("\n")
# 清空单元格默认内容
cell.text = lines[0]
# 循环设置段落内容
for i in range(1, len(lines)):
# 添加段落
paragraph = cell.add_paragraph(lines[i])
# # 设置段落行间距
paragraph_format = paragraph.paragraph_format
# 设置首行缩进为20字符
if i == len(lines) - 2:
paragraph_format.space_after = Pt(12) # 段后 0.5 行
# 修改第 14 行和第 15 行第 0 列的内容
texts = [
('院\n(系)\n意\n见', table.cell(14, 0)),
('学\n校\n意\n见', table.cell(15, 0))
]
for text, cell in texts:
# 按换行符拆分内容
lines = text.split("\n")
# 清空默认内容
cell.text = lines[0]
for line in lines[1:]:
# 添加段落
paragraph = cell.add_paragraph(line)
# 设置字体大小
for run in paragraph.runs:
run.font.size = Pt(12)
# 设置段落行间距
paragraph_format = paragraph.paragraph_format
paragraph_format.space_before = Pt(10) # 段前 0.5 行
paragraph_format.space_after = Pt(10) # 段后 0.5 行
paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
# 定义内容
content = (
"\n" * 6 + "院系主管学生工作领导签名:\n(院系公章)\n" + "\t\t年\t月\t日"
)
# 获取目标单元格
cell = table.cell(14, 1)
# 按换行符拆分内容
lines = content.split("\n")
# 清空单元格默认内容
cell.text = lines[0]
# 循环设置段落内容
for i in range(1, len(lines)):
# 添加段落
paragraph = cell.add_paragraph(lines[i])
# # 设置段落行间距
paragraph_format = paragraph.paragraph_format
# 设置不同段落的格式
if i == len(lines) - 3: # 倒数第三段
paragraph_format.space_before = Pt(6) # 段前 0.5 行
paragraph_format.space_after = Pt(12) # 段后 1 行
elif i == len(lines) - 1 or i == len(lines) - 2: # 倒数第一段和第二段
paragraph_format.space_before = Pt(12) # 段前 1 行
paragraph_format.space_after = None # 段后默认
# 获取目标单元格
cell = table.cell(15, 1)
# 清空单元格所有段落,移除默认的空段落
for paragraph in cell.paragraphs:
p_element = paragraph._element # 获取段落的 XML 元素
p_element.getparent().remove(p_element) # 从 XML 树中移除该段落
# 设置第一段内容
first_paragraph = cell.add_paragraph()
# 拆分第一段内容为普通文本和空格
text_parts = [
"经评审,并在校内",
" ",
"月",
" ",
"日至",
" ",
"月",
" ",
"日公示",
" ",
"个工作日,无异议,现报请批准该同学获得国家奖学金。",
]
# 遍历文本部分,添加到段落中
for part in text_parts:
run = first_paragraph.add_run(part)
if part.strip() == "": # 检查是否是空格部分
run.underline = WD_UNDERLINE.SINGLE # 设置下划线
else:
run.underline = False # 普通文本无下划线
# 设置第一段的段落格式
first_paragraph_format = first_paragraph.paragraph_format
first_paragraph_format.line_spacing = Pt(25) # 固定行距 25 磅
first_paragraph_format.space_before = Pt(12) # 段前间距 1 行(假设单倍行距为 12pt)
first_paragraph_format.space_after = Pt(6) # 段后间距 0.5 行
# 定义其余段落内容
content = (
"\n\n(学校公章)\n\n" + "\t\t年\t月\t日"
)
# 按换行符拆分内容
lines = content.split("\n")
# 循环设置其余段落
for i, line in enumerate(lines):
paragraph = cell.add_paragraph(line)
paragraph_format = paragraph.paragraph_format
paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
# 设置单元格垂直居中
tc = cell._tc # 获取底层 XML 元素
tcPr = tc.get_or_add_tcPr() # 获取或创建 <w:tcPr> 元素
vAlign = OxmlElement("w:vAlign") # 创建 <w:vAlign> 元素
vAlign.set(qn("w:val"), "center") # 设置为垂直居中
tcPr.append(vAlign)
# 设置第一列所有单元格的字体为小四、加粗
for row in table.rows:
cell = row.cells[0]
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(12) # 小四字体
run.font.bold = True # 加粗
# 设置段落水平居中
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
# 设置单元格垂直居中
tc = cell._tc # 获取底层 XML 元素
tcPr = tc.get_or_add_tcPr() # 获取或创建 <w:tcPr> 元素
vAlign = OxmlElement('w:vAlign') # 创建 <w:vAlign> 元素
vAlign.set(qn('w:val'), 'center') # 使用 qn 方法设置命名空间
tcPr.append(vAlign)
# 设置前十二行,除第一列外的所有单元格,去除加粗并设置字体大小,同时设置垂直居中
for row_index, row in enumerate(table.rows):
if row_index < 12: # 只处理前12行
for col_index, cell in enumerate(row.cells):
if col_index != 0: # 跳过第一列
# 设置字体大小
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(12) # 小四字体
# 设置段落的水平对齐
if row_index == 5 or row_index == 6: # 第6行和第7行从索引5开始
# 第6行和第7行左对齐
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
else:
# 其他行居中
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
# 设置单元格内容垂直居中
tc = cell._tc # 获取底层 XML 元素
tcPr = tc.get_or_add_tcPr() # 获取或创建 <w:tcPr> 元素
vAlign = OxmlElement('w:vAlign') # 创建 <w:vAlign> 元素
vAlign.set(qn('w:val'), 'center') # 设置为垂直居中
tcPr.append(vAlign)
2.6 使用pywin32美化文档
2.6.1 Word 应用初始化与文档加载
- 使用
pywin32
调用Word.Application
并创建 Word 应用实例。 - 设置 Word 应用窗口为不可见(
Visible = False
)。 - 打开指定路径的 Word 文档。
# 使用 pywin32 打开文档
word = win32.Dispatch("Word.Application")
word.Visible = False # 不显示Word窗口,设置为True可以查看
# 打开现有文档
doc = word.Documents.Open(doc_path)
2.6.2 文档表格对象获取与操作
获取文档中的第一个表格(doc.Tables(1)
)。
# 获取文档的选择对象
selection = word.Selection
# 获取表格
table = doc.Tables(1) # 获取第一个表格
2.6.3 设置表格单元格宽度
- 前 5 行第 2 列:宽度设置为 2.24 厘米。
- 前 4 行特定列:设置第 3 至第 7 列的宽度(分别为 3.07 厘米、1.91 厘米、3.6 厘米、2.62 厘米、2.74 厘米)。
- 第 5 行第 3-20 列:宽度按比例设置为 0.77 厘米。
- 第 6-7 行特定列:第 2 列和第 3 列的宽度分别设置为 7.6 厘米和 8.58 厘米。
- 第 8-12 行特定列:第 2、3、4 列分别设置为 2.71 厘米、6.93 厘米、6.54 厘米。
- 第 13-16 行第 2 列:宽度统一设置为 16.18 厘米。
# 初始化 pint 单位注册表
ureg = UnitRegistry()
def cm_to_points_with_pint(cm):
"""
使用 pint 将厘米转换为磅
"""
cm_value = cm * ureg.centimeter # 定义厘米单位
points = cm_value.to(ureg.point) # 转换为磅
return points.magnitude # 返回数值部分
# 设置第1-5行第2列的宽度为2.24厘米(1厘米 = 28.35磅)
for row in range(1, 6): # 行从1到5
cell = table.Cell(row, 2)
cell.Width = cm_to_points_with_pint(2.24) # 转换为磅
# 设置第1-4行各列的宽度
for row in range(1, 5):
cell_1 = table.Cell(row, 3)
cell_1.Width = cm_to_points_with_pint(3.07) # 转换为磅
cell_2 = table.Cell(row, 4)
cell_2.Width = cm_to_points_with_pint(1.91) # 转换为磅
cell_3 = table.Cell(row, 5)
cell_3.Width = cm_to_points_with_pint(3.6) # 转换为磅
cell_4 = table.Cell(row, 6)
cell_4.Width = cm_to_points_with_pint(2.62) # 转换为磅
cell_5 = table.Cell(row, 7)
cell_5.Width = cm_to_points_with_pint(2.74) # 转换为磅
# 设置第5行第3-20列的宽度为0.77厘米
for col in range(3, 21): # 列从第3到第20
cell = table.Cell(5, col)
cell.Width = cm_to_points_with_pint((16.18 - 2.24) / 18) # 转换为磅
for row in range(6, 8):
cell_1 = table.Cell(row, 2)
cell_1.Width = cm_to_points_with_pint(7.6) # 转换为磅
cell_2 = table.Cell(row, 3)
cell_2.Width = cm_to_points_with_pint(8.58) # 转换为磅
for row in range(8, 13):
cell_1 = table.Cell(row, 2)
cell_1.Width = cm_to_points_with_pint(2.71) # 转换为磅
cell_2 = table.Cell(row, 3)
cell_2.Width = cm_to_points_with_pint(6.93) # 转换为磅
cell_3 = table.Cell(row, 4)
cell_3.Width = cm_to_points_with_pint(6.54) # 转换为磅
for row in range(13, 17):
cell = table.Cell(row, 2)
cell.Width = cm_to_points_with_pint(16.18)
# 遍历单元格中的段落,设置字体为小四
for paragraph in cell.Range.Paragraphs:
paragraph.Range.Font.Size = 12 # 小四对应的字号为12pt
2.6.4 设置表格段落格式
遍历第 13-16 行第 2 列的段落,将其字体设置为小四(Font.Size = 12
),并根据对应段落数设置段首缩进(单位为字符数)
# 获取第16列第2行单元格
cell = table.Cell(13, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 3: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 2).Format.CharacterUnitFirstLineIndent = 21
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 获取第16列第2行单元格
cell = table.Cell(14, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 2: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 1).Format.CharacterUnitFirstLineIndent = 14.5
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 获取第16列第2行单元格
cell = table.Cell(15, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 3: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 2).Format.CharacterUnitFirstLineIndent = 14.5
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 2: # 检查是否至少有 2 个段落
paragraphs(paragraphs.Count - 1).Format.CharacterUnitFirstLineIndent = 23.5
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 获取第16列第2行单元格
cell = table.Cell(16, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置第一段首行缩进为 2 字符
if paragraphs.Count >= 1: # 检查是否存在段落
paragraphs(1).Format.CharacterUnitFirstLineIndent = 2
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 3: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 2).Format.CharacterUnitFirstLineIndent = 23.5
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
2.6.5 文档保存与关闭
保存对文档的更改,并关闭文档对象和 Word 应用程序实例。
# 保存并关闭文档
doc.Save()
doc.Close()
word.Quit()
2.7 最终生成结果
3. 完整代码
from docx import Document
from docx.shared import Pt, Cm
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT, WD_LINE_SPACING, WD_UNDERLINE
from docx.oxml import OxmlElement, parse_xml
from docx.oxml.ns import qn
import win32com.client as win32
from pint import UnitRegistry
# 初始化 pint 单位注册表
ureg = UnitRegistry()
def cm_to_points_with_pint(cm):
"""
使用 pint 将厘米转换为磅
"""
cm_value = cm * ureg.centimeter # 定义厘米单位
points = cm_value.to(ureg.point) # 转换为磅
return points.magnitude # 返回数值部分
# 设置文档网格(行网格和字符网格)
def set_document_grid(section, lines_per_page=46, characters_per_line=46, line_spacing=15.6, char_spacing=10.5):
"""
设置页面的行网格和字符网格
"""
# 获取 section 的页面设置(sectPr)
sectPr = section._sectPr
# 创建并配置 w:docGrid 元素
docGrid = OxmlElement('w:docGrid')
# 设置每页行数
docGrid.set(qn('w:count'), str(lines_per_page)) # 每页行数
# 设置字符网格(每行字符数)
docGrid.set(qn('w:charSpacing'), str(int(char_spacing * 20))) # 字符间距(通过磅转换)
# 设置行网格间距
docGrid.set(qn('w:linePitch'), str(int(line_spacing * 20))) # 行距
# 设置网格类型为行网格
docGrid.set(qn('w:type'), 'lines')
# 将 w:docGrid 添加到 section 的页面设置中
sectPr.append(docGrid)
def add_text_with_underline(paragraph, texts_with_styles):
"""
为段落添加指定文本及其样式(带或不带下划线)。
:param paragraph: docx 的段落对象
:param texts_with_styles: [(text, underline)] 列表,text 是文本内容,underline 是布尔值,是否带下划线
"""
for text, underline in texts_with_styles:
run = paragraph.add_run(text)
run.font.underline = underline
# 创建一个新的文档
doc = Document()
# 获取文档的第一个节(section)
section = doc.sections[0]
# 设置页面大小为A4
section.page_width = Cm(21.0)
section.page_height = Cm(29.7)
# 设置页边距
section.left_margin = Cm(1.8)
section.right_margin = Cm(1.8)
section.top_margin = Cm(2.0)
section.bottom_margin = Cm(2.0)
# 设置页眉和页脚距边界的距离
section.header_distance = Cm(1.5) # 设置页眉距边界为1.5厘米
section.footer_distance = Cm(2.5) # 设置页脚距边界为2.5厘米
# 获取文档的默认样式(Normal 样式)
style = doc.styles['Normal']
# 设置字体为宋体(中文)和 Times New Roman(西文),字号为五号(10.5磅)
font = style.font
font.name = 'Times New Roman' # 设置西文字体为 Times New Roman
font.size = Pt(10.5) # 设置字号为五号(10.5 磅)
# 设置中文字体为宋体
r = style.element
rPr = r.get_or_add_rPr()
rFonts = OxmlElement('w:rFonts')
rFonts.set(qn('w:eastAsia'), '宋体') # 设置中文字体为宋体
rPr.append(rFonts)
# 调用设置网格的方法,设定每页46行,每行46字符
set_document_grid(section, lines_per_page=46, characters_per_line=46, line_spacing=15.6, char_spacing=10.5)
# 添加第一段文本
p1 = doc.add_paragraph("2023-2024学年国家奖学金申请审批表")
run = p1.runs[0]
# 设置字体为黑体
run.font.name = '黑体'
r = run._element
r.rPr.rFonts.set(qn('w:eastAsia'), '黑体') # 设置中文字体为黑体
# 设置字号为小二(即 18 磅)
run.font.size = Pt(18)
# 设置段落居中对齐
p1.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
p1.paragraph_format.space_before = Pt(0)
p1.paragraph_format.space_after = Pt(0)
# 设置单倍行距
p1.paragraph_format.line_spacing = 1.0 # 单倍行距
# 添加第二段并设置制表位
p2 = doc.add_paragraph()
tab_stops = p2.paragraph_format.tab_stops
# 使用点单位设置制表位,更适用于字符宽度计算
# 每个字符宽度一般约等于12磅,19.58字符、33.75字符和46.58字符的转换计算如下
char_width_pt = 10.5 # 每个字符约为12磅
tab_stops.add_tab_stop(Pt(19.58 * char_width_pt)) # 第一个制表位
tab_stops.add_tab_stop(Pt(33.75 * char_width_pt)) # 第二个制表位
tab_stops.add_tab_stop(Pt(46.58 * char_width_pt)) # 第三个制表位
# 添加文本
run = p2.add_run("学校:\t学号:")
run.font.name = '宋体'
r = run._element
r.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 设置中文字体为宋体
run.font.size = Pt(12) # 小四字号(12 磅)
run.bold = True # 加粗
# 设置段落对齐方式
p2.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
# 设置段前间距为一行(假设小四字号12磅为一行)、段后间距为0、单倍行距
p2.paragraph_format.space_before = Pt(15.6) # 段前间距为一行,小四字号12磅
p2.paragraph_format.space_after = Pt(0) # 段后间距为0
p2.paragraph_format.line_spacing = 1.0 # 单倍行距
# 添加一个表格
table = doc.add_table(rows=16, cols=20)
table.style = 'Table Grid'
# 设置第一列宽度为 1.25 厘米
for row in table.rows:
cell = row.cells[0]
cell.width = Cm(1.25)
# 定义行高的映射
row_heights = {
range(0, 5): 1.08, # 前五行高度 1.08 厘米
range(5, 7): 1.1, # 第 6、7 行高度 0.79 厘米
range(7, 12): 0.95, # 第 8-12 行高度 0.95 厘米
range(12, 13): 9.65, # 第 13 行高度 9.65 厘米
range(13, 14): 9.05, # 第 14 行高度 9.05 厘米
range(14, 15): 7.14, # 第 15 行高度 7.14 厘米
range(15, 16): 7.38, # 第 16 行高度 7.38 厘米
}
# 设置每一行的高度
for i, row in enumerate(table.rows):
for key_range, height in row_heights.items():
if i in key_range:
tr = row._tr # 获取底层的 <w:tr> 元素
trPr = tr.get_or_add_trPr() # 获取或创建 <w:trPr> 子元素
trHeight = OxmlElement('w:trHeight')
trHeight.set(qn('w:val'), str(int(height * 567))) # 转换为 twips 单位
trPr.append(trHeight)
# 填充表格内容并合并第一列的前 5 行
for i in range(1, 5):
cell = table.cell(i, 0)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(i - 1, 0))
for i in range(6, 7):
cell = table.cell(i, 0)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(i - 1, 0))
for i in range(8, 12):
cell = table.cell(i, 0)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(i - 1, 0))
for a in range(4):
for b in range(7, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
for a in range(5, 7):
for b in range(3, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
for a in range(7, 12):
for b in range(4, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
for a in range(12, 16):
for b in range(2, 20):
cell = table.cell(a, b)
# 合并当前单元格与上一行的单元格
cell.merge(table.cell(a, b - 1))
table.cell(0, 0).text = '基本情况'
table.cell(0, 1).text = '姓名'
table.cell(0, 3).text = '性别'
table.cell(0, 5).text = '出生年月'
table.cell(1, 1).text = '政治面貌'
table.cell(1, 3).text = '民族'
table.cell(1, 5).text = '入学时间'
table.cell(2, 1).text = '院系'
table.cell(2, 3).text = '专业'
table.cell(2, 5).text = '学制'
table.cell(3, 1).text = '年级'
table.cell(3, 3).text = '班级'
table.cell(3, 5).text = '联系电话'
table.cell(4, 1).text = '身份证号'
table.cell(5, 0).text = '学习情况'
# 第 5 行第 1 列内容
add_text_with_underline(
table.cell(5, 1).paragraphs[0],
[
('成绩排名:', False),
(' / ', True),
('(名次/总人数)', False)
]
)
# 第 5 行第 2 列内容
add_text_with_underline(
table.cell(5, 2).paragraphs[0],
[
('实行综合考评排名:', False),
(' ', True),
('(选填“是”或“否”)', False)
]
)
# 第 6 行第 1 列内容
add_text_with_underline(
table.cell(6, 1).paragraphs[0],
[
('必修课', False),
(' ', True),
('门,其中及格以上', False),
(' ', True),
('门', False)
]
)
# 第 6 行第 2 列内容
add_text_with_underline(
table.cell(6, 2).paragraphs[0],
[
('如是,排名:', False),
(' / ', True),
('(名次/总人数)', False)
]
)
table.cell(7, 0).text = '主要获奖情况'
table.cell(7, 1).text = '日期'
table.cell(7, 2).text = '奖项名称'
table.cell(7, 3).text = '颁奖单位'
table.cell(12, 0).text = '申请理由'
# 定义内容
content = (
"以第三人称从个人简介,思想素质,学习情况,考证情况,工作情况等方面撰写。" +"\n" * 13 +
"申请人签名(手签):\n\n" + "\t\t年\t月\t日"
)
# 获取目标单元格
cell = table.cell(12, 1)
# 按换行符拆分内容
lines = content.split("\n")
# 清空单元格默认内容
cell.text = lines[0]
# 循环设置段落内容
for i in range(1, len(lines)):
# 添加段落
paragraph = cell.add_paragraph(lines[i])
# # 设置段落行间距
paragraph_format = paragraph.paragraph_format
table.cell(13, 0).text = '推荐理由'
# 定义内容
content = (
"\n" * 12 + "推荐人(辅导员或班主任)签名:\n" + "\t\t年\t月\t日"
)
# 获取目标单元格
cell = table.cell(13, 1)
# 按换行符拆分内容
lines = content.split("\n")
# 清空单元格默认内容
cell.text = lines[0]
# 循环设置段落内容
for i in range(1, len(lines)):
# 添加段落
paragraph = cell.add_paragraph(lines[i])
# # 设置段落行间距
paragraph_format = paragraph.paragraph_format
# 设置首行缩进为20字符
if i == len(lines) - 2:
paragraph_format.space_after = Pt(12) # 段后 0.5 行
# 修改第 14 行和第 15 行第 0 列的内容
texts = [
('院\n(系)\n意\n见', table.cell(14, 0)),
('学\n校\n意\n见', table.cell(15, 0))
]
for text, cell in texts:
# 按换行符拆分内容
lines = text.split("\n")
# 清空默认内容
cell.text = lines[0]
for line in lines[1:]:
# 添加段落
paragraph = cell.add_paragraph(line)
# 设置字体大小
for run in paragraph.runs:
run.font.size = Pt(12)
# 设置段落行间距
paragraph_format = paragraph.paragraph_format
paragraph_format.space_before = Pt(10) # 段前 0.5 行
paragraph_format.space_after = Pt(10) # 段后 0.5 行
paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
# 定义内容
content = (
"\n" * 6 + "院系主管学生工作领导签名:\n(院系公章)\n" + "\t\t年\t月\t日"
)
# 获取目标单元格
cell = table.cell(14, 1)
# 按换行符拆分内容
lines = content.split("\n")
# 清空单元格默认内容
cell.text = lines[0]
# 循环设置段落内容
for i in range(1, len(lines)):
# 添加段落
paragraph = cell.add_paragraph(lines[i])
# # 设置段落行间距
paragraph_format = paragraph.paragraph_format
# 设置不同段落的格式
if i == len(lines) - 3: # 倒数第三段
paragraph_format.space_before = Pt(6) # 段前 0.5 行
paragraph_format.space_after = Pt(12) # 段后 1 行
elif i == len(lines) - 1 or i == len(lines) - 2: # 倒数第一段和第二段
paragraph_format.space_before = Pt(12) # 段前 1 行
paragraph_format.space_after = None # 段后默认
# 获取目标单元格
cell = table.cell(15, 1)
# 清空单元格所有段落,移除默认的空段落
for paragraph in cell.paragraphs:
p_element = paragraph._element # 获取段落的 XML 元素
p_element.getparent().remove(p_element) # 从 XML 树中移除该段落
# 设置第一段内容
first_paragraph = cell.add_paragraph()
# 拆分第一段内容为普通文本和空格
text_parts = [
"经评审,并在校内",
" ",
"月",
" ",
"日至",
" ",
"月",
" ",
"日公示",
" ",
"个工作日,无异议,现报请批准该同学获得国家奖学金。",
]
# 遍历文本部分,添加到段落中
for part in text_parts:
run = first_paragraph.add_run(part)
if part.strip() == "": # 检查是否是空格部分
run.underline = WD_UNDERLINE.SINGLE # 设置下划线
else:
run.underline = False # 普通文本无下划线
# 设置第一段的段落格式
first_paragraph_format = first_paragraph.paragraph_format
first_paragraph_format.line_spacing = Pt(25) # 固定行距 25 磅
first_paragraph_format.space_before = Pt(12) # 段前间距 1 行(假设单倍行距为 12pt)
first_paragraph_format.space_after = Pt(6) # 段后间距 0.5 行
# 定义其余段落内容
content = (
"\n\n(学校公章)\n\n" + "\t\t年\t月\t日"
)
# 按换行符拆分内容
lines = content.split("\n")
# 循环设置其余段落
for i, line in enumerate(lines):
paragraph = cell.add_paragraph(line)
paragraph_format = paragraph.paragraph_format
paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
# 设置单元格垂直居中
tc = cell._tc # 获取底层 XML 元素
tcPr = tc.get_or_add_tcPr() # 获取或创建 <w:tcPr> 元素
vAlign = OxmlElement("w:vAlign") # 创建 <w:vAlign> 元素
vAlign.set(qn("w:val"), "center") # 设置为垂直居中
tcPr.append(vAlign)
# 设置第一列所有单元格的字体为小四、加粗
for row in table.rows:
cell = row.cells[0]
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(12) # 小四字体
run.font.bold = True # 加粗
# 设置段落水平居中
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
# 设置单元格垂直居中
tc = cell._tc # 获取底层 XML 元素
tcPr = tc.get_or_add_tcPr() # 获取或创建 <w:tcPr> 元素
vAlign = OxmlElement('w:vAlign') # 创建 <w:vAlign> 元素
vAlign.set(qn('w:val'), 'center') # 使用 qn 方法设置命名空间
tcPr.append(vAlign)
# 设置前十二行,除第一列外的所有单元格,去除加粗并设置字体大小,同时设置垂直居中
for row_index, row in enumerate(table.rows):
if row_index < 12: # 只处理前12行
for col_index, cell in enumerate(row.cells):
if col_index != 0: # 跳过第一列
# 设置字体大小
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(12) # 小四字体
# 设置段落的水平对齐
if row_index == 5 or row_index == 6: # 第6行和第7行从索引5开始
# 第6行和第7行左对齐
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
else:
# 其他行居中
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
# 设置单元格内容垂直居中
tc = cell._tc # 获取底层 XML 元素
tcPr = tc.get_or_add_tcPr() # 获取或创建 <w:tcPr> 元素
vAlign = OxmlElement('w:vAlign') # 创建 <w:vAlign> 元素
vAlign.set(qn('w:val'), 'center') # 设置为垂直居中
tcPr.append(vAlign)
# 在文档中添加一段文本
p3 = doc.add_paragraph("制表:全国学生资助管理中心 2024版")
# 设置段落格式
paragraph_format = p3.paragraph_format
paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT # 居右对齐
paragraph_format.right_indent = Cm(0.37) # 右侧缩进 0.37 厘米
paragraph_format.space_before = Pt(5.25)
paragraph_format.space_after = 0 # 段后间距 0
paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE # 单倍行距
# 设置字体大小为五号
run = p3.runs[0]
run.font.size = Pt(10.5) # 五号字体大小为 10.5 磅
# 保存文档到指定位置
doc_path = r"D:\MSIPC\Documents\国家奖学金申请审批表(测试).docx"
# 保存文档
doc.save(doc_path)
# 使用 pywin32 打开文档
word = win32.Dispatch("Word.Application")
word.Visible = False # 不显示Word窗口,设置为True可以查看
# 打开现有文档
doc = word.Documents.Open(doc_path)
# 获取文档的选择对象
selection = word.Selection
# 获取表格
table = doc.Tables(1) # 获取第一个表格
# 设置第1-5行第2列的宽度为2.24厘米(1厘米 = 28.35磅)
for row in range(1, 6): # 行从1到5
cell = table.Cell(row, 2)
cell.Width = cm_to_points_with_pint(2.24) # 转换为磅
# 设置第1-4行各列的宽度
for row in range(1, 5):
cell_1 = table.Cell(row, 3)
cell_1.Width = cm_to_points_with_pint(3.07) # 转换为磅
cell_2 = table.Cell(row, 4)
cell_2.Width = cm_to_points_with_pint(1.91) # 转换为磅
cell_3 = table.Cell(row, 5)
cell_3.Width = cm_to_points_with_pint(3.6) # 转换为磅
cell_4 = table.Cell(row, 6)
cell_4.Width = cm_to_points_with_pint(2.62) # 转换为磅
cell_5 = table.Cell(row, 7)
cell_5.Width = cm_to_points_with_pint(2.74) # 转换为磅
# 设置第5行第3-20列的宽度为0.77厘米
for col in range(3, 21): # 列从第3到第20
cell = table.Cell(5, col)
cell.Width = cm_to_points_with_pint((16.18 - 2.24) / 18) # 转换为磅
for row in range(6, 8):
cell_1 = table.Cell(row, 2)
cell_1.Width = cm_to_points_with_pint(7.6) # 转换为磅
cell_2 = table.Cell(row, 3)
cell_2.Width = cm_to_points_with_pint(8.58) # 转换为磅
for row in range(8, 13):
cell_1 = table.Cell(row, 2)
cell_1.Width = cm_to_points_with_pint(2.71) # 转换为磅
cell_2 = table.Cell(row, 3)
cell_2.Width = cm_to_points_with_pint(6.93) # 转换为磅
cell_3 = table.Cell(row, 4)
cell_3.Width = cm_to_points_with_pint(6.54) # 转换为磅
for row in range(13, 17):
cell = table.Cell(row, 2)
cell.Width = cm_to_points_with_pint(16.18)
# 遍历单元格中的段落,设置字体为小四
for paragraph in cell.Range.Paragraphs:
paragraph.Range.Font.Size = 12 # 小四对应的字号为12pt
# 获取第16列第2行单元格
cell = table.Cell(13, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 3: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 2).Format.CharacterUnitFirstLineIndent = 21
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 获取第16列第2行单元格
cell = table.Cell(14, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 2: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 1).Format.CharacterUnitFirstLineIndent = 14.5
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 获取第16列第2行单元格
cell = table.Cell(15, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 3: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 2).Format.CharacterUnitFirstLineIndent = 14.5
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 2: # 检查是否至少有 2 个段落
paragraphs(paragraphs.Count - 1).Format.CharacterUnitFirstLineIndent = 23.5
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 获取第16列第2行单元格
cell = table.Cell(16, 2)
# 获取单元格中的段落
paragraphs = cell.Range.Paragraphs
# 设置第一段首行缩进为 2 字符
if paragraphs.Count >= 1: # 检查是否存在段落
paragraphs(1).Format.CharacterUnitFirstLineIndent = 2
# 设置倒数第3段首行缩进为 23.5 字符
if paragraphs.Count >= 3: # 检查是否至少有 3 个段落
paragraphs(paragraphs.Count - 2).Format.CharacterUnitFirstLineIndent = 23.5
# 设置倒数第1段首行缩进为 18.83 字符
if paragraphs.Count >= 1: # 检查是否至少有 1 个段落
paragraphs(paragraphs.Count).Format.CharacterUnitFirstLineIndent = 18.83
# 保存并关闭文档
doc.Save()
doc.Close()
word.Quit()