在上一章中,我们介绍了使用自定义模板导出文档的基础用法:

https://blog.csdn.net/u013071014/article/details/150958008?spm=1001.2014.3001.5501

本章中,我们介绍一下表格、段落、图表等用jinja2模板设计语法及导出。

1. 文本

  1. 模板

    {{ report_title }}
    报告时间:{{ report_time }}
    

    文本的样式,在word模板中先设计好

    在这里插入图片描述

  2. 数据

    {
    	"report_title": "测试报告",
    	"report_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }
    

    在这里插入图片描述

2. 表格

  1. 模板

    在这里插入图片描述

  2. 数据

    在这里插入图片描述

3. 循环段落

  1. 模板

    2.段落{% for paragraph in paragraphs %}
    2.{{ loop.index }} {{ paragraph.title }}
    {{ paragraph.content }}
    {% endfor %}
    

    文本的样式,在word模板中先设计好

    模板中在title前面有个编号,loop.index(从 1 开始)和 loop.index0(从 0 开始)

    在这里插入图片描述

  2. 数据

    {
    	"paragraphs": [
    		{"title": "段落标题1", "content": "这是段落标题1的内容"},
            {"title": "段落标题2", "content": "这是段落标题2的内容"},
            {"title": "段落标题3", "content": "这是段落标题3的内容"}
        ]
    }
    

    在这里插入图片描述

  3. 注意:

    {% for paragraph in paragraphs %} 放在了标题后面而不是另起一行,是因为在渲染的时候,会被当做一个空行,如下图:

    在这里插入图片描述

    在这里插入图片描述

4. 图表

可以使用Matplotlib创建图表,然后插入到文档指定位置

pip install matplotlib numpy python-docx

4.1 饼状图

  1. 模板

    {{ pie_chart }}
    

    在这里插入图片描述

  2. 关键代码片段

    # 设置中文字体支持
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    
    
    def create_pie_chart(data, labels, title,  doc):
        """创建饼状图并返回图像对象"""
        # 创建图表
        fig, ax = plt.subplots(figsize=(6, 6))
    
        # 绘制饼状图
        wedges, texts, autotexts = ax.pie(
            data,
            labels=labels,
            autopct='%1.1f%%',
            startangle=90,
            colors=plt.cm.Paired(range(len(data)))
        )
    
        # 设置标题
        ax.set_title(title, fontsize=16, fontweight='bold')
    
        # 确保饼图是圆形
        ax.axis('equal')
    
        # 保存图表到字节流
        chart_stream = BytesIO()
        plt.savefig(chart_stream, format='png', dpi=300, bbox_inches='tight')
        plt.close(fig)
    
        # 重置流位置
        chart_stream.seek(0)
    
        # 返回InlineImage对象
        return InlineImage(doc, chart_stream, width=Mm(120))
        
    

    在这里插入图片描述

4.2 柱状图

  1. 模板

    {{ bar_chart }}
    

    在这里插入图片描述

  2. 关键代码片段

    def create_bar_chart(data, labels, title, xlabel, ylabel, doc):
        """创建柱状图并返回图像对象"""
        # 创建图表
        fig, ax = plt.subplots(figsize=(8, 6))
    
        # 设置位置和宽度
        x_pos = np.arange(len(labels))
    
        # 绘制柱状图
        bars = ax.bar(x_pos, data, color=plt.cm.Paired(range(len(data))))
    
        # 设置标题和标签
        ax.set_title(title, fontsize=16, fontweight='bold')
        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
        ax.set_xticks(x_pos)
        ax.set_xticklabels(labels)
    
        # 在柱子上方添加数值标签
        for bar in bars:
            height = bar.get_height()
            ax.text(bar.get_x() + bar.get_width() / 2., height + 0.05,
                    f'{height}', ha='center', va='bottom')
    
        # 自动调整布局
        plt.tight_layout()
    
        # 保存图表到字节流
        chart_stream = BytesIO()
        plt.savefig(chart_stream, format='png', dpi=300, bbox_inches='tight')
        plt.close(fig)
    
        # 重置流位置
        chart_stream.seek(0)
    
        # 返回InlineImage对象
        return InlineImage(doc, chart_stream, width=Mm(120))
    

    在这里插入图片描述

4.3 折线图

  1. 模板

    {{ line_chart }}
    

    在这里插入图片描述

  2. 关键代码片段

    def create_line_chart(data, labels, title, xlabel, ylabel, doc):
        """创建折线图并返回图像对象"""
        # 创建图表
        fig, ax = plt.subplots(figsize=(8, 6))
    
        # 设置位置
        x_pos = np.arange(len(labels))
    
        # 绘制折线图
        line = ax.plot(x_pos, data, marker='o', linestyle='-', linewidth=2, markersize=8)
    
        # 设置标题和标签
        ax.set_title(title, fontsize=16, fontweight='bold')
        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
        ax.set_xticks(x_pos)
        ax.set_xticklabels(labels)
    
        # 在数据点上添加数值标签
        for i, value in enumerate(data):
            ax.text(i, value + 0.1, f'{value}', ha='center', va='bottom')
    
        # 添加网格
        ax.grid(True, linestyle='--', alpha=0.7)
    
        # 自动调整布局
        plt.tight_layout()
    
        # 保存图表到字节流
        chart_stream = BytesIO()
        plt.savefig(chart_stream, format='png', dpi=300, bbox_inches='tight')
        plt.close(fig)
    
        # 重置流位置
        chart_stream.seek(0)
    
        # 返回InlineImage对象
        return InlineImage(doc, chart_stream, width=Mm(120))
    

    在这里插入图片描述

5. 完整模板及代码

在这里插入图片描述

pip install docxtpl matplotlib numpy python-docx
from datetime import datetime

from docxtpl import DocxTemplate, InlineImage
import json
from docx.shared import Mm
from matplotlib import pyplot as plt
from io import BytesIO
import numpy as np



def generate_report(doc, json_data, output_path):

    # 加载 JSON 数据
    context = json.loads(json_data) if isinstance(json_data, str) else json_data

    # 渲染模板
    doc.render(context)

    # 保存为新文件
    doc.save(output_path)
    print(f"报告已生成:{output_path}")

# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

def create_pie_chart(data, labels, title,  doc):
    """创建饼状图并返回图像对象"""
    # 创建图表
    fig, ax = plt.subplots(figsize=(6, 6))

    # 绘制饼状图
    wedges, texts, autotexts = ax.pie(
        data,
        labels=labels,
        autopct='%1.1f%%',
        startangle=90,
        colors=plt.cm.Paired(range(len(data)))
    )

    # 设置标题
    ax.set_title(title, fontsize=16, fontweight='bold')

    # 确保饼图是圆形
    ax.axis('equal')

    # 保存图表到字节流
    chart_stream = BytesIO()
    plt.savefig(chart_stream, format='png', dpi=300, bbox_inches='tight')
    plt.close(fig)

    # 重置流位置
    chart_stream.seek(0)

    # 返回InlineImage对象
    return InlineImage(doc, chart_stream, width=Mm(120))


def create_bar_chart(data, labels, title, xlabel, ylabel, doc):
    """创建柱状图并返回图像对象"""
    # 创建图表
    fig, ax = plt.subplots(figsize=(8, 6))

    # 设置位置和宽度
    x_pos = np.arange(len(labels))

    # 绘制柱状图
    bars = ax.bar(x_pos, data, color=plt.cm.Paired(range(len(data))))

    # 设置标题和标签
    ax.set_title(title, fontsize=16, fontweight='bold')
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_xticks(x_pos)
    ax.set_xticklabels(labels)

    # 在柱子上方添加数值标签
    for bar in bars:
        height = bar.get_height()
        ax.text(bar.get_x() + bar.get_width() / 2., height + 0.05,
                f'{height}', ha='center', va='bottom')

    # 自动调整布局
    plt.tight_layout()

    # 保存图表到字节流
    chart_stream = BytesIO()
    plt.savefig(chart_stream, format='png', dpi=300, bbox_inches='tight')
    plt.close(fig)

    # 重置流位置
    chart_stream.seek(0)

    # 返回InlineImage对象
    return InlineImage(doc, chart_stream, width=Mm(120))


def create_line_chart(data, labels, title, xlabel, ylabel, doc):
    """创建折线图并返回图像对象"""
    # 创建图表
    fig, ax = plt.subplots(figsize=(8, 6))

    # 设置位置
    x_pos = np.arange(len(labels))

    # 绘制折线图
    line = ax.plot(x_pos, data, marker='o', linestyle='-', linewidth=2, markersize=8)

    # 设置标题和标签
    ax.set_title(title, fontsize=16, fontweight='bold')
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_xticks(x_pos)
    ax.set_xticklabels(labels)

    # 在数据点上添加数值标签
    for i, value in enumerate(data):
        ax.text(i, value + 0.1, f'{value}', ha='center', va='bottom')

    # 添加网格
    ax.grid(True, linestyle='--', alpha=0.7)

    # 自动调整布局
    plt.tight_layout()

    # 保存图表到字节流
    chart_stream = BytesIO()
    plt.savefig(chart_stream, format='png', dpi=300, bbox_inches='tight')
    plt.close(fig)

    # 重置流位置
    chart_stream.seek(0)

    # 返回InlineImage对象
    return InlineImage(doc, chart_stream, width=Mm(120))

# 使用示例
if __name__ == "__main__":
    template_file = "./template/test-template02.docx"
    output_file = "./output/test-output02.docx"

    # 加载模板
    doc = DocxTemplate(template_file)

    # 饼状图数据
    pie_chart_data = {
        'labels': ['产品A', '产品B', '产品C', '产品D', '其他'],
        'values': [35, 25, 20, 15, 5],
        'title': '产品销售额分布'
    }
    # 创建饼状图
    pie_chart = create_pie_chart(
        pie_chart_data['values'],
        pie_chart_data['labels'],
        pie_chart_data['title'],
        doc
    )

    # 柱状图数据
    bar_chart_data = {
        'data': [120, 150, 90, 180, 110],
        'labels': ['一月', '二月', '三月', '四月', '五月'],
        'title': '月度销售额',
        'xlabel': '月份',
        'ylabel': '销售额 (万元)'
    }
    bar_chart = create_bar_chart(
        bar_chart_data['data'],
        bar_chart_data['labels'],
        bar_chart_data['title'],
        bar_chart_data['xlabel'],
        bar_chart_data['ylabel'],
        doc
    )

    # 折线图数据
    line_chart_data = {
        'data': [120, 150, 90, 180, 110, 200, 170],
        'labels': ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
        'title': '周销售额趋势',
        'xlabel': '星期',
        'ylabel': '销售额 (万元)'
    }
    line_chart = create_line_chart(
        line_chart_data['data'],
        line_chart_data['labels'],
        line_chart_data['title'],
        line_chart_data['xlabel'],
        line_chart_data['ylabel'],
        doc
    )


    # 模拟 JSON 数据
    data = {
        "report_title": "测试报告",
        "report_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "server_infos": [
            {"server_name": "server01", "ip": "192.168.10.5", "port": 80},
            {"server_name": "server02", "ip": "192.168.10.6", "port": 80},
            {"server_name": "server03", "ip": "192.168.10.7", "port": 80}
        ],
        "paragraphs": [
            {"title": "段落标题1", "content": "这是段落标题1的内容"},
            {"title": "段落标题2", "content": "这是段落标题2的内容"},
            {"title": "段落标题3", "content": "这是段落标题3的内容"}
        ],
        "pie_chart": pie_chart,
        "bar_chart": bar_chart,
        "line_chart": line_chart
    }

    generate_report(doc, data, output_file)

更多推荐