v2 4.9 该版本集成了Excel导出功能

This commit is contained in:
赖力诚 committed 2026-04-09 15:19:48 +08:00
1 parent e5030abf39
commit 86567eece5
16 files changed
+2516 -30

No files matched your search

+250
View File
@@ -0,0 +1,250 @@
import os
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from PIL import Image as PILImage
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, Image as RLImage, PageBreak
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.platypus import TableStyle
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics
from reportlab.lib.pagesizes import A4
class NutritionPDFBuilder:
def __init__(self):
pdfmetrics.registerFont(TTFont('SimHei', 'C:/Windows/Fonts/simhei.ttf'))
def build(self, data, output_path):
doc = SimpleDocTemplate(output_path, pagesize=A4)
story = []
# ===== 统一字体 =====
normal = ParagraphStyle(
"normal",
fontName="SimHei",
fontSize=13,
leading=18,
alignment=1
)
header = ParagraphStyle(
"header",
fontName="SimHei",
fontSize=13,
leading=18,
alignment=1,
textColor=colors.white
)
story.append(Paragraph("营养健康评估报告", normal))
story.append(Spacer(1, 10 * mm))
# ================= 食物表 =================
food_table = []
food_table.append([Paragraph("食物摄入量评估报告", header), "", "", ""])
food_table.append([
Paragraph("种类", header),
Paragraph("图片", header),
Paragraph("摄入量", header),
Paragraph("评价", header)
])
for cat, info in data["category_result"].items():
food_table.append([
Paragraph(cat, normal),
self._get_image(cat),
Paragraph(f"{info['value']} g", normal),
Paragraph(info["evaluation"], normal)
])
# ===== 建议(适中行距)=====
suggestion_text = "<br/>".join(
[f"{i+1}. {s}" for i, s in enumerate(data["suggestions"])]
)
food_table.append([
Paragraph("整体评估及建议", normal),
Paragraph(suggestion_text, normal),
"",
""
])
t1 = Table(
food_table,
colWidths=[40*mm, 55*mm, 40*mm, 30*mm],
repeatRows=2
)
t1.setStyle(TableStyle([
("SPAN", (0,0), (-1,0)),
("SPAN", (1,-1), (-1,-1)),
("BACKGROUND", (0,0), (-1,1), colors.HexColor("#4F75B5")),
("TEXTCOLOR", (0,0), (-1,1), colors.white),
("GRID",(0,0),(-1,-1),0.5,colors.grey),
("FONTNAME",(0,0),(-1,-1),"SimHei"),
("ALIGN",(0,0),(-1,-1),"CENTER"),
("VALIGN",(0,0),(-1,-1),"MIDDLE"),
("TOPPADDING",(0,0),(-1,-1),14),
("BOTTOMPADDING",(0,0),(-1,-1),14),
("ROWHEIGHT",(0,2),(-1,-2),45)
]))
story.append(t1)
story.append(PageBreak())
# ================= 饼图 =================
pie_path = "temp_pie.png"
self._generate_pie(data, pie_path)
n = data["nutrition"]
energy_table = [
[Paragraph("能量及钙摄入量评估报告", header), "", ""],
[
Paragraph("项目", header),
Paragraph("摄入量", header),
Paragraph("评价", header)
],
[
Paragraph("总能量", normal),
Paragraph(f"{n['energy']['value']} kcal/day", normal),
Paragraph(n["energy"]["evaluation"], normal)
],
[
Paragraph("三大营养素供能占比", normal),
self._get_pie(pie_path),
""
],
[
Paragraph("钙", normal),
Paragraph(f"{n['calcium']['value']} mg/day", normal),
Paragraph(n["calcium"]["evaluation"], normal)
],
]
# ===== 解读(适中间距)=====
interpretation = []
if n["energy"]["evaluation"] == "不足":
interpretation.append("能量摄入不足,应增加碳水化合物摄入")
if n["calcium"]["evaluation"] == "不足":
interpretation.append("钙摄入不足,应增加奶制品摄入")
text = "<br/>".join(interpretation)
energy_table.append([
Paragraph("解读", normal),
Paragraph(text, normal),
""
])
t2 = Table(energy_table, colWidths=[50*mm, 70*mm, 40*mm])
t2.setStyle(TableStyle([
("SPAN", (0,0), (-1,0)),
("SPAN", (1,3), (2,3)),
("SPAN", (1,-1), (2,-1)),
("BACKGROUND",(0,0),(-1,1),colors.HexColor("#4F75B5")),
("TEXTCOLOR",(0,0),(-1,1),colors.white),
("GRID",(0,0),(-1,-1),0.5,colors.grey),
("FONTNAME",(0,0),(-1,-1),"SimHei"),
("ALIGN",(0,0),(-1,-1),"CENTER"),
("VALIGN",(0,0),(-1,-1),"MIDDLE"),
("TOPPADDING",(0,0),(-1,-1),14),
("BOTTOMPADDING",(0,0),(-1,-1),14),
]))
story.append(t2)
doc.build(story)
# ================= 图片 =================
def _get_image(self, category):
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
path = os.path.join(base, "static", "images", f"{category}.png")
if os.path.exists(path):
return RLImage(path, width=35*mm, height=35*mm)
return ""
# ================= 饼图 =================
def _generate_pie(self, data, path):
font_path = r"C:\Windows\Fonts\simhei.ttf"
font_prop = fm.FontProperties(fname=font_path)
protein = float(data["nutrition"].get("protein", 0))
fat = float(data["nutrition"].get("fat", 0))
carb = float(data["nutrition"].get("carbohydrate", 0))
protein_kcal = protein * 4
fat_kcal = fat * 9
carb_kcal = carb * 4
total = protein_kcal + fat_kcal + carb_kcal
values = [1,1,1] if total == 0 else [
protein_kcal / total,
fat_kcal / total,
carb_kcal / total
]
labels = [
f"蛋白质 {protein}g",
f"脂肪 {fat}g",
f"碳水 {carb}g"
]
plt.figure(figsize=(6,6))
plt.gca().set_aspect('equal')
plt.pie(
values,
labels=labels,
autopct="%1.1f%%",
startangle=90,
textprops={'fontproperties': font_prop, 'fontsize': 12}
)
plt.title("三大营养素供能占比", fontproperties=font_prop)
plt.savefig(path, bbox_inches='tight')
plt.close()
# ================= 饼图插入 =================
def _get_pie(self, path):
if not os.path.exists(path):
return ""
img = PILImage.open(path)
w, h = img.size
target_w = 90 * mm
target_h = target_w * (h / w)
return RLImage(path, width=target_w, height=target_h)