装修公司每天要出十几份报价单,手工在 Word 里画表格、填单价、算合价、加合计,既慢又容易算错。本文用纯 Python(python-docx + openpyxl)把"Excel 报价源数据"一键转成格式统一、带合计行的 Word 文档,适合做报价系统的后端模板。
一、为什么不用直接复制 Excel
Excel 转到 Word 大多靠手工粘贴,丢格式、丢边框、合价列还经常算错。我们的目标:源数据在 Excel(设计/施工/主材分 sheet),脚本读出来,自动生成一份排版规范的 .docx,每个分项一行,末行自动算"工程总价"。
二、依赖与准备
bash
pip install python-docx openpyxl
源 Excel 约定三列:项目 / 单价(元) / 工程量,另可选 单位。合价 = 单价 × 工程量,由脚本计算,避免手填出错。
三、核心代码
python
from docx import Document
from docx.shared import Pt, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
import openpyxl
def read_items(path):
wb = openpyxl.load_workbook(path)
ws = wb.active
items = []
for row in ws.iter_rows(min_row=2, values_only=True):
name, price, qty, unit = row[0], row[1], row[2], row[3] or '项'
if name is None:
continue
items.append((name, float(price), float(qty), unit,
round(float(price) * float(qty), 2)))
return items
def build_docx(items, out):
doc = Document()
# 标题
h = doc.add_heading('装修工程报价单', level=0)
h.alignment = WD_ALIGN_PARAGRAPH.CENTER
table = doc.add_table(rows=1, cols=5)
table.style = 'Light Grid Accent 1'
table.alignment = WD_TABLE_ALIGNMENT.CENTER
hdr = table.rows[0].cells
for i, t in enumerate(['项目', '单价(元)', '工程量', '单位', '合价(元)']):
hdr[i].text = t
total = 0.0
for name, price, qty, unit, sub in items:
cells = table.add_row().cells
cells[0].text = str(name)
cells[1].text = f'{price:.2f}'
cells[2].text = f'{qty:.2f}'
cells[3].text = str(unit)
cells[4].text = f'{sub:.2f}'
total += sub
# 合计行(加粗)
sum_row = table.add_row().cells
sum_row[0].text = '工程总价'
sum_row[4].text = f'{total:.2f}'
for c in sum_row:
for p in c.paragraphs:
for r in p.runs:
r.font.bold = True
# 数字列右对齐、字号统一
for row in table.rows:
for ci in (1, 2, 4):
cell = row.cells[ci]
for p in cell.paragraphs:
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
for r in p.runs:
r.font.size = Pt(10)
doc.save(out)
print('saved', out, '| 总价', round(total, 2))
if __name__ == '__main__':
items = read_items('quote.xlsx')
build_docx(items, '报价单.docx')
四、三个容易踩的坑
- 中文路径报编码错误 :openpyxl/python-docx 本身是 Unicode 安全的,报错大多来自日志里手拼路径。统一用原始字符串
r'...'或pathlib.Path,别用gbk硬解码。 - 合计行被当成数据行 :
iter_rows(min_row=2)已经跳过表头,但若 Excel 末尾有空行,要用if name is None: continue过滤,否则会出现一行空单价把总价算崩。 - 表格样式不生效 :
table.style只接受 Word 内置样式名(如Light Grid Accent 1),写错名字会静默失败、表格无边框。不确定的话用doc.styles打印可用名再选。
把这套逻辑封成接口,配合前端表单,就能让客户在线填完需求立马下载一份规范的报价单------这正是 maixuzi.cn 在线报价系统在后端做的事。更多装修数字化工具模板见 maixuzi.cn/resources/