first commit 稳定版本,已知该版本存在问题,前端未对数据进行校验,没有集成导出excel
This commit is contained in:
commit
e5030abf39
40 files changed
+5186
No files matched your search
+250
@@ -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.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)
|
||||
Reference in new issue
Block a user