580 lines
20 KiB
Python
580 lines
20 KiB
Python
|
|
"""
|
|||
|
|
简化的餐食记录界面
|
|||
|
|
使用选择式输入,减少用户负担
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import tkinter as tk
|
|||
|
|
from tkinter import ttk, messagebox
|
|||
|
|
import customtkinter as ctk
|
|||
|
|
from typing import Dict, List, Optional
|
|||
|
|
from smart_food.smart_database import (
|
|||
|
|
search_foods, get_food_categories, get_foods_by_category,
|
|||
|
|
get_portion_options, estimate_calories, record_meal_smart
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class SmartMealRecordDialog:
|
|||
|
|
"""智能餐食记录对话框"""
|
|||
|
|
|
|||
|
|
def __init__(self, parent, user_id: str, meal_type: str = "lunch"):
|
|||
|
|
self.parent = parent
|
|||
|
|
self.user_id = user_id
|
|||
|
|
self.meal_type = meal_type
|
|||
|
|
self.selected_foods = []
|
|||
|
|
|
|||
|
|
# 创建对话框
|
|||
|
|
self.dialog = ctk.CTkToplevel(parent)
|
|||
|
|
self.dialog.title(f"记录{self._get_meal_name(meal_type)}")
|
|||
|
|
self.dialog.geometry("600x700")
|
|||
|
|
self.dialog.transient(parent)
|
|||
|
|
self.dialog.grab_set()
|
|||
|
|
|
|||
|
|
# 居中显示
|
|||
|
|
self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))
|
|||
|
|
|
|||
|
|
self._create_widgets()
|
|||
|
|
|
|||
|
|
def _get_meal_name(self, meal_type: str) -> str:
|
|||
|
|
"""获取餐次中文名称"""
|
|||
|
|
meal_names = {
|
|||
|
|
"breakfast": "早餐",
|
|||
|
|
"lunch": "午餐",
|
|||
|
|
"dinner": "晚餐"
|
|||
|
|
}
|
|||
|
|
return meal_names.get(meal_type, "餐食")
|
|||
|
|
|
|||
|
|
def _create_widgets(self):
|
|||
|
|
"""创建界面组件"""
|
|||
|
|
# 主框架
|
|||
|
|
main_frame = ctk.CTkScrollableFrame(self.dialog)
|
|||
|
|
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
|
|||
|
|
|
|||
|
|
# 标题
|
|||
|
|
title_label = ctk.CTkLabel(
|
|||
|
|
main_frame,
|
|||
|
|
text=f"🍽️ 记录{self._get_meal_name(self.meal_type)}",
|
|||
|
|
font=ctk.CTkFont(size=20, weight="bold")
|
|||
|
|
)
|
|||
|
|
title_label.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 食物选择区域
|
|||
|
|
self._create_food_selection(main_frame)
|
|||
|
|
|
|||
|
|
# 已选食物列表
|
|||
|
|
self._create_selected_foods(main_frame)
|
|||
|
|
|
|||
|
|
# 满意度评分
|
|||
|
|
self._create_satisfaction_rating(main_frame)
|
|||
|
|
|
|||
|
|
# 备注
|
|||
|
|
self._create_notes_section(main_frame)
|
|||
|
|
|
|||
|
|
# 按钮区域
|
|||
|
|
self._create_buttons(main_frame)
|
|||
|
|
|
|||
|
|
def _create_food_selection(self, parent):
|
|||
|
|
"""创建食物选择区域"""
|
|||
|
|
# 食物选择框架
|
|||
|
|
food_frame = ctk.CTkFrame(parent)
|
|||
|
|
food_frame.pack(fill="x", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 标题
|
|||
|
|
food_title = ctk.CTkLabel(
|
|||
|
|
food_frame,
|
|||
|
|
text="选择食物",
|
|||
|
|
font=ctk.CTkFont(size=16, weight="bold")
|
|||
|
|
)
|
|||
|
|
food_title.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 食物搜索
|
|||
|
|
search_frame = ctk.CTkFrame(food_frame)
|
|||
|
|
search_frame.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
search_label = ctk.CTkLabel(search_frame, text="搜索食物:")
|
|||
|
|
search_label.pack(anchor="w", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
self.search_var = tk.StringVar()
|
|||
|
|
self.search_entry = ctk.CTkEntry(search_frame, textvariable=self.search_var, placeholder_text="输入食物名称搜索...")
|
|||
|
|
self.search_entry.pack(fill="x", padx=5, pady=2)
|
|||
|
|
self.search_entry.bind("<KeyRelease>", self._on_search_changed)
|
|||
|
|
|
|||
|
|
# 搜索结果
|
|||
|
|
self.search_results_frame = ctk.CTkFrame(food_frame)
|
|||
|
|
self.search_results_frame.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
self.search_results_label = ctk.CTkLabel(self.search_results_frame, text="搜索结果:")
|
|||
|
|
self.search_results_label.pack(anchor="w", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
self.search_results_menu = ctk.CTkOptionMenu(
|
|||
|
|
self.search_results_frame,
|
|||
|
|
values=[],
|
|||
|
|
command=self._on_search_result_selected
|
|||
|
|
)
|
|||
|
|
self.search_results_menu.pack(fill="x", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
# 分隔线
|
|||
|
|
separator = ctk.CTkFrame(food_frame, height=2)
|
|||
|
|
separator.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
# 分类选择
|
|||
|
|
category_frame = ctk.CTkFrame(food_frame)
|
|||
|
|
category_frame.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
category_label = ctk.CTkLabel(category_frame, text="食物分类:")
|
|||
|
|
category_label.pack(side="left", padx=5)
|
|||
|
|
|
|||
|
|
self.category_var = tk.StringVar(value="主食")
|
|||
|
|
self.category_menu = ctk.CTkOptionMenu(
|
|||
|
|
category_frame,
|
|||
|
|
variable=self.category_var,
|
|||
|
|
values=get_food_categories(),
|
|||
|
|
command=self._on_category_changed
|
|||
|
|
)
|
|||
|
|
self.category_menu.pack(side="left", padx=5)
|
|||
|
|
|
|||
|
|
# 食物选择
|
|||
|
|
food_select_frame = ctk.CTkFrame(food_frame)
|
|||
|
|
food_select_frame.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
food_label = ctk.CTkLabel(food_select_frame, text="选择食物:")
|
|||
|
|
food_label.pack(anchor="w", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
self.food_var = tk.StringVar()
|
|||
|
|
self.food_menu = ctk.CTkOptionMenu(
|
|||
|
|
food_select_frame,
|
|||
|
|
variable=self.food_var,
|
|||
|
|
values=[]
|
|||
|
|
)
|
|||
|
|
self.food_menu.pack(fill="x", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
# 分量选择
|
|||
|
|
portion_frame = ctk.CTkFrame(food_frame)
|
|||
|
|
portion_frame.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
portion_label = ctk.CTkLabel(portion_frame, text="分量:")
|
|||
|
|
portion_label.pack(anchor="w", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
self.portion_var = tk.StringVar(value="适量")
|
|||
|
|
self.portion_menu = ctk.CTkOptionMenu(
|
|||
|
|
portion_frame,
|
|||
|
|
variable=self.portion_var,
|
|||
|
|
values=["适量"]
|
|||
|
|
)
|
|||
|
|
self.portion_menu.pack(fill="x", padx=5, pady=2)
|
|||
|
|
|
|||
|
|
# 添加按钮
|
|||
|
|
add_button = ctk.CTkButton(
|
|||
|
|
food_frame,
|
|||
|
|
text="添加到餐食",
|
|||
|
|
command=self._add_food,
|
|||
|
|
width=150
|
|||
|
|
)
|
|||
|
|
add_button.pack(pady=10)
|
|||
|
|
|
|||
|
|
# AI分析按钮
|
|||
|
|
ai_analyze_button = ctk.CTkButton(
|
|||
|
|
food_frame,
|
|||
|
|
text="AI分析食物",
|
|||
|
|
command=self._analyze_food_with_ai,
|
|||
|
|
width=150,
|
|||
|
|
fg_color="green"
|
|||
|
|
)
|
|||
|
|
ai_analyze_button.pack(pady=5)
|
|||
|
|
|
|||
|
|
# 初始化食物列表
|
|||
|
|
self._on_category_changed("主食")
|
|||
|
|
|
|||
|
|
def _create_selected_foods(self, parent):
|
|||
|
|
"""创建已选食物列表"""
|
|||
|
|
# 已选食物框架
|
|||
|
|
selected_frame = ctk.CTkFrame(parent)
|
|||
|
|
selected_frame.pack(fill="x", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 标题
|
|||
|
|
selected_title = ctk.CTkLabel(
|
|||
|
|
selected_frame,
|
|||
|
|
text="已选食物",
|
|||
|
|
font=ctk.CTkFont(size=16, weight="bold")
|
|||
|
|
)
|
|||
|
|
selected_title.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 食物列表
|
|||
|
|
self.foods_listbox = tk.Listbox(selected_frame, height=6)
|
|||
|
|
self.foods_listbox.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
# 删除按钮
|
|||
|
|
delete_button = ctk.CTkButton(
|
|||
|
|
selected_frame,
|
|||
|
|
text="删除选中",
|
|||
|
|
command=self._remove_food,
|
|||
|
|
width=150
|
|||
|
|
)
|
|||
|
|
delete_button.pack(pady=5)
|
|||
|
|
|
|||
|
|
def _create_satisfaction_rating(self, parent):
|
|||
|
|
"""创建满意度评分"""
|
|||
|
|
# 满意度框架
|
|||
|
|
satisfaction_frame = ctk.CTkFrame(parent)
|
|||
|
|
satisfaction_frame.pack(fill="x", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 标题
|
|||
|
|
satisfaction_title = ctk.CTkLabel(
|
|||
|
|
satisfaction_frame,
|
|||
|
|
text="满意度评分",
|
|||
|
|
font=ctk.CTkFont(size=16, weight="bold")
|
|||
|
|
)
|
|||
|
|
satisfaction_title.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 评分滑块
|
|||
|
|
self.satisfaction_var = tk.IntVar(value=3)
|
|||
|
|
satisfaction_slider = ctk.CTkSlider(
|
|||
|
|
satisfaction_frame,
|
|||
|
|
from_=1,
|
|||
|
|
to=5,
|
|||
|
|
number_of_steps=4,
|
|||
|
|
variable=self.satisfaction_var
|
|||
|
|
)
|
|||
|
|
satisfaction_slider.pack(fill="x", padx=20, pady=10)
|
|||
|
|
|
|||
|
|
# 评分标签
|
|||
|
|
self.satisfaction_label = ctk.CTkLabel(
|
|||
|
|
satisfaction_frame,
|
|||
|
|
text="3分 - 一般",
|
|||
|
|
font=ctk.CTkFont(size=14)
|
|||
|
|
)
|
|||
|
|
self.satisfaction_label.pack(pady=5)
|
|||
|
|
|
|||
|
|
# 绑定滑块事件
|
|||
|
|
satisfaction_slider.configure(command=self._on_satisfaction_changed)
|
|||
|
|
|
|||
|
|
def _create_notes_section(self, parent):
|
|||
|
|
"""创建备注区域"""
|
|||
|
|
# 备注框架
|
|||
|
|
notes_frame = ctk.CTkFrame(parent)
|
|||
|
|
notes_frame.pack(fill="x", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 标题
|
|||
|
|
notes_title = ctk.CTkLabel(
|
|||
|
|
notes_frame,
|
|||
|
|
text="备注 (可选)",
|
|||
|
|
font=ctk.CTkFont(size=16, weight="bold")
|
|||
|
|
)
|
|||
|
|
notes_title.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 备注输入
|
|||
|
|
self.notes_text = ctk.CTkTextbox(notes_frame, height=60)
|
|||
|
|
self.notes_text.pack(fill="x", padx=10, pady=5)
|
|||
|
|
self.notes_text.insert("1.0", "可以记录一些感受或特殊说明...")
|
|||
|
|
|
|||
|
|
def _create_buttons(self, parent):
|
|||
|
|
"""创建按钮区域"""
|
|||
|
|
# 按钮框架
|
|||
|
|
button_frame = ctk.CTkFrame(parent)
|
|||
|
|
button_frame.pack(fill="x", padx=10, pady=20)
|
|||
|
|
|
|||
|
|
# 保存按钮
|
|||
|
|
save_button = ctk.CTkButton(
|
|||
|
|
button_frame,
|
|||
|
|
text="保存餐食记录",
|
|||
|
|
command=self._save_meal,
|
|||
|
|
width=150,
|
|||
|
|
height=40
|
|||
|
|
)
|
|||
|
|
save_button.pack(side="left", padx=20, pady=10)
|
|||
|
|
|
|||
|
|
# 取消按钮
|
|||
|
|
cancel_button = ctk.CTkButton(
|
|||
|
|
button_frame,
|
|||
|
|
text="取消",
|
|||
|
|
command=self._cancel,
|
|||
|
|
width=150,
|
|||
|
|
height=40
|
|||
|
|
)
|
|||
|
|
cancel_button.pack(side="right", padx=20, pady=10)
|
|||
|
|
|
|||
|
|
def _on_search_changed(self, event=None):
|
|||
|
|
"""搜索输入改变事件"""
|
|||
|
|
query = self.search_var.get().strip()
|
|||
|
|
if not query:
|
|||
|
|
self.search_results_menu.configure(values=[])
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
from smart_food.smart_database import search_foods
|
|||
|
|
results = search_foods(query)
|
|||
|
|
if results:
|
|||
|
|
food_names = [result["name"] for result in results]
|
|||
|
|
self.search_results_menu.configure(values=food_names)
|
|||
|
|
self.search_results_label.configure(text=f"搜索结果 ({len(food_names)}个):")
|
|||
|
|
else:
|
|||
|
|
self.search_results_menu.configure(values=[])
|
|||
|
|
self.search_results_label.configure(text="未找到匹配的食物")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.search_results_menu.configure(values=[])
|
|||
|
|
self.search_results_label.configure(text="搜索失败")
|
|||
|
|
|
|||
|
|
def _on_search_result_selected(self, food_name):
|
|||
|
|
"""搜索结果选择事件"""
|
|||
|
|
self.food_var.set(food_name)
|
|||
|
|
self._on_food_changed(food_name)
|
|||
|
|
|
|||
|
|
# 自动选择对应的分类
|
|||
|
|
try:
|
|||
|
|
from smart_food.smart_database import get_food_categories, get_foods_by_category
|
|||
|
|
categories = get_food_categories()
|
|||
|
|
for category in categories:
|
|||
|
|
foods_in_category = get_foods_by_category(category)
|
|||
|
|
if food_name in foods_in_category:
|
|||
|
|
self.category_var.set(category)
|
|||
|
|
self._on_category_changed(category)
|
|||
|
|
break
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def _analyze_food_with_ai(self):
|
|||
|
|
"""使用AI分析食物"""
|
|||
|
|
food_name = self.food_var.get()
|
|||
|
|
portion = self.portion_var.get()
|
|||
|
|
|
|||
|
|
if not food_name:
|
|||
|
|
messagebox.showwarning("警告", "请选择食物")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
from smart_food.smart_database import analyze_food_with_ai
|
|||
|
|
|
|||
|
|
# 显示分析进度
|
|||
|
|
self._show_ai_analysis_dialog(food_name, portion)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
messagebox.showerror("错误", f"AI分析失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def _show_ai_analysis_dialog(self, food_name: str, portion: str):
|
|||
|
|
"""显示AI分析对话框"""
|
|||
|
|
# 创建分析对话框
|
|||
|
|
analysis_dialog = ctk.CTkToplevel(self.dialog)
|
|||
|
|
analysis_dialog.title(f"AI分析 - {food_name}")
|
|||
|
|
analysis_dialog.geometry("500x600")
|
|||
|
|
analysis_dialog.transient(self.dialog)
|
|||
|
|
analysis_dialog.grab_set()
|
|||
|
|
|
|||
|
|
# 居中显示
|
|||
|
|
analysis_dialog.geometry("+%d+%d" % (self.dialog.winfo_rootx() + 50, self.dialog.winfo_rooty() + 50))
|
|||
|
|
|
|||
|
|
# 创建滚动框架
|
|||
|
|
scroll_frame = ctk.CTkScrollableFrame(analysis_dialog)
|
|||
|
|
scroll_frame.pack(fill="both", expand=True, padx=20, pady=20)
|
|||
|
|
|
|||
|
|
# 标题
|
|||
|
|
title_label = ctk.CTkLabel(
|
|||
|
|
scroll_frame,
|
|||
|
|
text=f"🤖 AI分析: {food_name}",
|
|||
|
|
font=ctk.CTkFont(size=18, weight="bold")
|
|||
|
|
)
|
|||
|
|
title_label.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 分析进度
|
|||
|
|
progress_label = ctk.CTkLabel(scroll_frame, text="正在分析中...")
|
|||
|
|
progress_label.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 分析结果区域
|
|||
|
|
result_text = ctk.CTkTextbox(scroll_frame, height=400, width=450)
|
|||
|
|
result_text.pack(fill="both", expand=True, pady=10)
|
|||
|
|
|
|||
|
|
# 关闭按钮
|
|||
|
|
close_button = ctk.CTkButton(
|
|||
|
|
scroll_frame,
|
|||
|
|
text="关闭",
|
|||
|
|
command=analysis_dialog.destroy,
|
|||
|
|
width=100
|
|||
|
|
)
|
|||
|
|
close_button.pack(pady=10)
|
|||
|
|
|
|||
|
|
# 在后台线程中执行AI分析
|
|||
|
|
import threading
|
|||
|
|
|
|||
|
|
def analyze_thread():
|
|||
|
|
try:
|
|||
|
|
from smart_food.smart_database import analyze_food_with_ai
|
|||
|
|
|
|||
|
|
# 执行AI分析
|
|||
|
|
result = analyze_food_with_ai(food_name, portion)
|
|||
|
|
|
|||
|
|
# 更新UI
|
|||
|
|
analysis_dialog.after(0, lambda: self._update_ai_analysis_result(
|
|||
|
|
analysis_dialog, result_text, progress_label, result
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
analysis_dialog.after(0, lambda: self._update_ai_analysis_error(
|
|||
|
|
analysis_dialog, result_text, progress_label, str(e)
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
threading.Thread(target=analyze_thread, daemon=True).start()
|
|||
|
|
|
|||
|
|
def _update_ai_analysis_result(self, dialog, result_text, progress_label, result):
|
|||
|
|
"""更新AI分析结果"""
|
|||
|
|
progress_label.configure(text="分析完成")
|
|||
|
|
|
|||
|
|
if result.get('success'):
|
|||
|
|
content = f"""
|
|||
|
|
🍎 食物分析结果: {result.get('reasoning', 'AI分析')}
|
|||
|
|
|
|||
|
|
📊 营养成分:
|
|||
|
|
- 热量: {result.get('calories', 0)} 卡路里
|
|||
|
|
- 蛋白质: {result.get('protein', 0):.1f} 克
|
|||
|
|
- 碳水化合物: {result.get('carbs', 0):.1f} 克
|
|||
|
|
- 脂肪: {result.get('fat', 0):.1f} 克
|
|||
|
|
- 纤维: {result.get('fiber', 0):.1f} 克
|
|||
|
|
|
|||
|
|
🏷️ 分类: {result.get('category', '其他')}
|
|||
|
|
|
|||
|
|
💡 健康建议:
|
|||
|
|
"""
|
|||
|
|
for tip in result.get('health_tips', []):
|
|||
|
|
content += f"• {tip}\n"
|
|||
|
|
|
|||
|
|
content += "\n👨🍳 制作建议:\n"
|
|||
|
|
for suggestion in result.get('cooking_suggestions', []):
|
|||
|
|
content += f"• {suggestion}\n"
|
|||
|
|
|
|||
|
|
content += f"\n🎯 置信度: {result.get('confidence', 0.5):.1%}"
|
|||
|
|
else:
|
|||
|
|
content = "AI分析失败,请稍后重试。"
|
|||
|
|
|
|||
|
|
result_text.delete("1.0", "end")
|
|||
|
|
result_text.insert("1.0", content)
|
|||
|
|
|
|||
|
|
def _update_ai_analysis_error(self, dialog, result_text, progress_label, error_msg):
|
|||
|
|
"""更新AI分析错误"""
|
|||
|
|
progress_label.configure(text="分析失败")
|
|||
|
|
result_text.delete("1.0", "end")
|
|||
|
|
result_text.insert("1.0", f"AI分析失败: {error_msg}")
|
|||
|
|
|
|||
|
|
def _on_category_changed(self, category):
|
|||
|
|
"""分类改变事件"""
|
|||
|
|
foods = get_foods_by_category(category)
|
|||
|
|
self.food_menu.configure(values=foods)
|
|||
|
|
if foods:
|
|||
|
|
self.food_var.set(foods[0])
|
|||
|
|
self._on_food_changed(foods[0])
|
|||
|
|
|
|||
|
|
def _on_food_changed(self, food_name):
|
|||
|
|
"""食物改变事件"""
|
|||
|
|
portions = get_portion_options(food_name)
|
|||
|
|
self.portion_menu.configure(values=portions)
|
|||
|
|
if portions:
|
|||
|
|
self.portion_var.set(portions[0])
|
|||
|
|
|
|||
|
|
def _add_food(self):
|
|||
|
|
"""添加食物"""
|
|||
|
|
food_name = self.food_var.get()
|
|||
|
|
portion = self.portion_var.get()
|
|||
|
|
|
|||
|
|
if not food_name:
|
|||
|
|
messagebox.showwarning("警告", "请选择食物")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 估算热量
|
|||
|
|
calories = estimate_calories(food_name, portion)
|
|||
|
|
|
|||
|
|
# 添加到列表
|
|||
|
|
food_item = {
|
|||
|
|
"name": food_name,
|
|||
|
|
"portion": portion,
|
|||
|
|
"calories": calories
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.selected_foods.append(food_item)
|
|||
|
|
self._update_foods_list()
|
|||
|
|
|
|||
|
|
def _update_foods_list(self):
|
|||
|
|
"""更新食物列表显示"""
|
|||
|
|
self.foods_listbox.delete(0, tk.END)
|
|||
|
|
|
|||
|
|
total_calories = 0
|
|||
|
|
for i, food_item in enumerate(self.selected_foods):
|
|||
|
|
display_text = f"{food_item['name']} - {food_item['portion']} ({food_item['calories']}卡)"
|
|||
|
|
self.foods_listbox.insert(tk.END, display_text)
|
|||
|
|
total_calories += food_item['calories']
|
|||
|
|
|
|||
|
|
# 显示总热量
|
|||
|
|
if self.selected_foods:
|
|||
|
|
total_text = f"总热量: {total_calories}卡路里"
|
|||
|
|
self.foods_listbox.insert(tk.END, "")
|
|||
|
|
self.foods_listbox.insert(tk.END, total_text)
|
|||
|
|
|
|||
|
|
def _remove_food(self):
|
|||
|
|
"""删除选中的食物"""
|
|||
|
|
selection = self.foods_listbox.curselection()
|
|||
|
|
if not selection:
|
|||
|
|
messagebox.showwarning("警告", "请选择要删除的食物")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
index = selection[0]
|
|||
|
|
if index < len(self.selected_foods):
|
|||
|
|
self.selected_foods.pop(index)
|
|||
|
|
self._update_foods_list()
|
|||
|
|
|
|||
|
|
def _on_satisfaction_changed(self, value):
|
|||
|
|
"""满意度改变事件"""
|
|||
|
|
score = int(float(value))
|
|||
|
|
score_texts = {
|
|||
|
|
1: "1分 - 很不满意",
|
|||
|
|
2: "2分 - 不满意",
|
|||
|
|
3: "3分 - 一般",
|
|||
|
|
4: "4分 - 满意",
|
|||
|
|
5: "5分 - 很满意"
|
|||
|
|
}
|
|||
|
|
self.satisfaction_label.configure(text=score_texts.get(score, "3分 - 一般"))
|
|||
|
|
|
|||
|
|
def _save_meal(self):
|
|||
|
|
"""保存餐食记录"""
|
|||
|
|
if not self.selected_foods:
|
|||
|
|
messagebox.showwarning("警告", "请至少添加一种食物")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 构建餐食数据
|
|||
|
|
meal_data = {
|
|||
|
|
"meal_type": self.meal_type,
|
|||
|
|
"foods": self.selected_foods,
|
|||
|
|
"satisfaction_score": self.satisfaction_var.get(),
|
|||
|
|
"notes": self.notes_text.get("1.0", "end-1c").strip()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 智能记录餐食
|
|||
|
|
if record_meal_smart(self.user_id, meal_data):
|
|||
|
|
messagebox.showinfo("成功", "餐食记录保存成功!")
|
|||
|
|
self.dialog.destroy()
|
|||
|
|
else:
|
|||
|
|
messagebox.showerror("错误", "餐食记录保存失败")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
messagebox.showerror("错误", f"保存失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def _cancel(self):
|
|||
|
|
"""取消记录"""
|
|||
|
|
self.dialog.destroy()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 便捷函数
|
|||
|
|
def show_smart_meal_record_dialog(parent, user_id: str, meal_type: str = "lunch"):
|
|||
|
|
"""显示智能餐食记录对话框"""
|
|||
|
|
dialog = SmartMealRecordDialog(parent, user_id, meal_type)
|
|||
|
|
parent.wait_window(dialog.dialog)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# 测试智能餐食记录对话框
|
|||
|
|
root = tk.Tk()
|
|||
|
|
root.title("测试智能餐食记录")
|
|||
|
|
|
|||
|
|
def test_dialog():
|
|||
|
|
show_smart_meal_record_dialog(root, "test_user", "lunch")
|
|||
|
|
|
|||
|
|
test_button = tk.Button(root, text="测试餐食记录", command=test_dialog)
|
|||
|
|
test_button.pack(pady=20)
|
|||
|
|
|
|||
|
|
root.mainloop()
|