Files
recommend/gui/quick_user_input.py

527 lines
20 KiB
Python
Raw Normal View History

"""
快速用户需求录入界面
优化用户录入流程提高效率
"""
import tkinter as tk
from tkinter import ttk, messagebox
import customtkinter as ctk
from typing import Dict, List, Optional
from datetime import datetime
import json
class QuickUserInputDialog:
"""快速用户需求录入对话框"""
def __init__(self, parent, user_id: str):
self.parent = parent
self.user_id = user_id
self.input_data = {}
# 创建对话框
self.dialog = ctk.CTkToplevel(parent)
self.dialog.title("快速需求录入")
self.dialog.geometry("800x600")
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 _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="🚀 快速需求录入",
font=ctk.CTkFont(size=24, weight="bold")
)
title_label.pack(pady=20)
# 步骤1基础信息
self._create_basic_info_section(main_frame)
# 步骤2饮食偏好
self._create_preferences_section(main_frame)
# 步骤3健康目标
self._create_health_goals_section(main_frame)
# 步骤4快速餐食记录
self._create_quick_meal_section(main_frame)
# 按钮区域
self._create_buttons(main_frame)
def _create_basic_info_section(self, parent):
"""创建基础信息区域"""
section_frame = ctk.CTkFrame(parent)
section_frame.pack(fill="x", padx=10, pady=10)
# 标题
title = ctk.CTkLabel(
section_frame,
text="1⃣ 基础信息",
font=ctk.CTkFont(size=18, weight="bold")
)
title.pack(pady=10)
# 信息网格
info_frame = ctk.CTkFrame(section_frame)
info_frame.pack(fill="x", padx=20, pady=10)
# 姓名
name_label = ctk.CTkLabel(info_frame, text="姓名:")
name_label.grid(row=0, column=0, sticky="w", padx=10, pady=5)
self.name_var = tk.StringVar()
name_entry = ctk.CTkEntry(info_frame, textvariable=self.name_var, width=200)
name_entry.grid(row=0, column=1, sticky="w", padx=10, pady=5)
# 年龄范围
age_label = ctk.CTkLabel(info_frame, text="年龄范围:")
age_label.grid(row=0, column=2, sticky="w", padx=10, pady=5)
self.age_var = tk.StringVar(value="25-30岁")
age_menu = ctk.CTkOptionMenu(
info_frame,
variable=self.age_var,
values=["18-24岁", "25-30岁", "31-35岁", "36-40岁", "41-45岁", "46-50岁", "51-55岁", "56-60岁", "60岁以上"]
)
age_menu.grid(row=0, column=3, sticky="w", padx=10, pady=5)
# 性别
gender_label = ctk.CTkLabel(info_frame, text="性别:")
gender_label.grid(row=1, column=0, sticky="w", padx=10, pady=5)
self.gender_var = tk.StringVar(value="")
gender_menu = ctk.CTkOptionMenu(
info_frame,
variable=self.gender_var,
values=["", ""]
)
gender_menu.grid(row=1, column=1, sticky="w", padx=10, pady=5)
# 身高体重范围
height_label = ctk.CTkLabel(info_frame, text="身高范围:")
height_label.grid(row=1, column=2, sticky="w", padx=10, pady=5)
self.height_var = tk.StringVar(value="160-165cm")
height_menu = ctk.CTkOptionMenu(
info_frame,
variable=self.height_var,
values=["150cm以下", "150-155cm", "155-160cm", "160-165cm", "165-170cm", "170-175cm", "175-180cm", "180cm以上"]
)
height_menu.grid(row=1, column=3, sticky="w", padx=10, pady=5)
weight_label = ctk.CTkLabel(info_frame, text="体重范围:")
weight_label.grid(row=2, column=0, sticky="w", padx=10, pady=5)
self.weight_var = tk.StringVar(value="50-55kg")
weight_menu = ctk.CTkOptionMenu(
info_frame,
variable=self.weight_var,
values=["40kg以下", "40-45kg", "45-50kg", "50-55kg", "55-60kg", "60-65kg", "65-70kg", "70-75kg", "75-80kg", "80kg以上"]
)
weight_menu.grid(row=2, column=1, sticky="w", padx=10, pady=5)
# 活动水平
activity_label = ctk.CTkLabel(info_frame, text="活动水平:")
activity_label.grid(row=2, column=2, sticky="w", padx=10, pady=5)
self.activity_var = tk.StringVar(value="中等")
activity_menu = ctk.CTkOptionMenu(
info_frame,
variable=self.activity_var,
values=["久坐", "轻度活动", "中等", "高度活动", "极度活动"]
)
activity_menu.grid(row=2, column=3, sticky="w", padx=10, pady=5)
def _create_preferences_section(self, parent):
"""创建饮食偏好区域"""
section_frame = ctk.CTkFrame(parent)
section_frame.pack(fill="x", padx=10, pady=10)
# 标题
title = ctk.CTkLabel(
section_frame,
text="2⃣ 饮食偏好",
font=ctk.CTkFont(size=18, weight="bold")
)
title.pack(pady=10)
# 偏好网格
pref_frame = ctk.CTkFrame(section_frame)
pref_frame.pack(fill="x", padx=20, pady=10)
# 口味偏好
taste_label = ctk.CTkLabel(pref_frame, text="主要口味偏好:")
taste_label.grid(row=0, column=0, sticky="w", padx=10, pady=5)
self.taste_var = tk.StringVar(value="均衡")
taste_menu = ctk.CTkOptionMenu(
pref_frame,
variable=self.taste_var,
values=["均衡", "偏甜", "偏咸", "偏辣", "偏酸", "偏清淡"]
)
taste_menu.grid(row=0, column=1, sticky="w", padx=10, pady=5)
# 饮食类型
diet_label = ctk.CTkLabel(pref_frame, text="饮食类型:")
diet_label.grid(row=0, column=2, sticky="w", padx=10, pady=5)
self.diet_var = tk.StringVar(value="普通饮食")
diet_menu = ctk.CTkOptionMenu(
pref_frame,
variable=self.diet_var,
values=["普通饮食", "素食", "低脂饮食", "低糖饮食", "高蛋白饮食", "地中海饮食"]
)
diet_menu.grid(row=0, column=3, sticky="w", padx=10, pady=5)
# 过敏食物
allergy_label = ctk.CTkLabel(pref_frame, text="过敏食物:")
allergy_label.grid(row=1, column=0, sticky="w", padx=10, pady=5)
self.allergy_var = tk.StringVar(value="")
allergy_menu = ctk.CTkOptionMenu(
pref_frame,
variable=self.allergy_var,
values=["", "花生", "海鲜", "牛奶", "鸡蛋", "坚果", "大豆", "小麦", "其他"]
)
allergy_menu.grid(row=1, column=1, sticky="w", padx=10, pady=5)
# 不喜欢食物
dislike_label = ctk.CTkLabel(pref_frame, text="不喜欢食物:")
dislike_label.grid(row=1, column=2, sticky="w", padx=10, pady=5)
self.dislike_var = tk.StringVar(value="")
dislike_menu = ctk.CTkOptionMenu(
pref_frame,
variable=self.dislike_var,
values=["", "内脏", "辛辣", "油腻", "甜食", "酸味", "苦味", "其他"]
)
dislike_menu.grid(row=1, column=3, sticky="w", padx=10, pady=5)
def _create_health_goals_section(self, parent):
"""创建健康目标区域"""
section_frame = ctk.CTkFrame(parent)
section_frame.pack(fill="x", padx=10, pady=10)
# 标题
title = ctk.CTkLabel(
section_frame,
text="3⃣ 健康目标",
font=ctk.CTkFont(size=18, weight="bold")
)
title.pack(pady=10)
# 目标选择
goals_frame = ctk.CTkFrame(section_frame)
goals_frame.pack(fill="x", padx=20, pady=10)
# 主要目标
main_goal_label = ctk.CTkLabel(goals_frame, text="主要目标:")
main_goal_label.grid(row=0, column=0, sticky="w", padx=10, pady=5)
self.main_goal_var = tk.StringVar(value="保持健康")
main_goal_menu = ctk.CTkOptionMenu(
goals_frame,
variable=self.main_goal_var,
values=["保持健康", "减重", "增重", "增肌", "改善消化", "提高免疫力", "控制血糖", "降低血压"]
)
main_goal_menu.grid(row=0, column=1, sticky="w", padx=10, pady=5)
# 次要目标
sub_goal_label = ctk.CTkLabel(goals_frame, text="次要目标:")
sub_goal_label.grid(row=0, column=2, sticky="w", padx=10, pady=5)
self.sub_goal_var = tk.StringVar(value="")
sub_goal_menu = ctk.CTkOptionMenu(
goals_frame,
variable=self.sub_goal_var,
values=["", "改善睡眠", "提高精力", "美容养颜", "延缓衰老", "改善皮肤", "增强记忆", "缓解压力"]
)
sub_goal_menu.grid(row=0, column=3, sticky="w", padx=10, pady=5)
def _create_quick_meal_section(self, parent):
"""创建快速餐食记录区域"""
section_frame = ctk.CTkFrame(parent)
section_frame.pack(fill="x", padx=10, pady=10)
# 标题
title = ctk.CTkLabel(
section_frame,
text="4⃣ 快速餐食记录",
font=ctk.CTkFont(size=18, weight="bold")
)
title.pack(pady=10)
# 餐食选择
meal_frame = ctk.CTkFrame(section_frame)
meal_frame.pack(fill="x", padx=20, pady=10)
# 餐次选择
meal_type_label = ctk.CTkLabel(meal_frame, text="餐次:")
meal_type_label.grid(row=0, column=0, sticky="w", padx=10, pady=5)
self.meal_type_var = tk.StringVar(value="午餐")
meal_type_menu = ctk.CTkOptionMenu(
meal_frame,
variable=self.meal_type_var,
values=["早餐", "午餐", "晚餐", "加餐"]
)
meal_type_menu.grid(row=0, column=1, sticky="w", padx=10, pady=5)
# 快速食物选择
food_label = ctk.CTkLabel(meal_frame, text="快速选择食物:")
food_label.grid(row=1, column=0, sticky="w", padx=10, pady=5)
self.quick_food_var = tk.StringVar(value="米饭+鸡肉+蔬菜")
quick_food_menu = ctk.CTkOptionMenu(
meal_frame,
variable=self.quick_food_var,
values=[
"米饭+鸡肉+蔬菜",
"面条+鸡蛋+青菜",
"馒头+豆腐+白菜",
"粥+咸菜+鸡蛋",
"面包+牛奶+水果",
"饺子+汤",
"包子+豆浆",
"其他"
]
)
quick_food_menu.grid(row=1, column=1, sticky="w", padx=10, pady=5)
# 分量选择
portion_label = ctk.CTkLabel(meal_frame, text="分量:")
portion_label.grid(row=1, column=2, sticky="w", padx=10, pady=5)
self.portion_var = tk.StringVar(value="正常")
portion_menu = ctk.CTkOptionMenu(
meal_frame,
variable=self.portion_var,
values=["少量", "正常", "较多", "很多"]
)
portion_menu.grid(row=1, column=3, sticky="w", padx=10, pady=5)
# 满意度
satisfaction_label = ctk.CTkLabel(meal_frame, text="满意度:")
satisfaction_label.grid(row=2, column=0, sticky="w", padx=10, pady=5)
self.satisfaction_var = tk.IntVar(value=3)
satisfaction_slider = ctk.CTkSlider(
meal_frame,
from_=1,
to=5,
number_of_steps=4,
variable=self.satisfaction_var
)
satisfaction_slider.grid(row=2, column=1, columnspan=2, sticky="ew", padx=10, pady=5)
# 满意度标签
self.satisfaction_display = ctk.CTkLabel(meal_frame, text="3分 - 一般")
self.satisfaction_display.grid(row=2, column=3, sticky="w", padx=10, pady=5)
# 绑定滑块事件
satisfaction_slider.configure(command=self._on_satisfaction_changed)
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_all_data,
width=200,
height=50,
font=ctk.CTkFont(size=16, weight="bold")
)
save_button.pack(side="left", padx=20, pady=10)
# 取消按钮
cancel_button = ctk.CTkButton(
button_frame,
text="❌ 取消",
command=self._cancel,
width=150,
height=50
)
cancel_button.pack(side="right", padx=20, pady=10)
def _on_satisfaction_changed(self, value):
"""满意度改变事件"""
score = int(float(value))
score_texts = {
1: "1分 - 很不满意",
2: "2分 - 不满意",
3: "3分 - 一般",
4: "4分 - 满意",
5: "5分 - 很满意"
}
self.satisfaction_display.configure(text=score_texts.get(score, "3分 - 一般"))
def _save_all_data(self):
"""保存所有数据"""
try:
# 收集所有数据
self.input_data = {
'basic_info': {
'name': self.name_var.get(),
'age_range': self.age_var.get(),
'gender': self.gender_var.get(),
'height_range': self.height_var.get(),
'weight_range': self.weight_var.get(),
'activity_level': self.activity_var.get()
},
'preferences': {
'taste': self.taste_var.get(),
'diet_type': self.diet_var.get(),
'allergies': self.allergy_var.get(),
'dislikes': self.dislike_var.get()
},
'health_goals': {
'main_goal': self.main_goal_var.get(),
'sub_goal': self.sub_goal_var.get()
},
'quick_meal': {
'meal_type': self.meal_type_var.get(),
'food_combo': self.quick_food_var.get(),
'portion': self.portion_var.get(),
'satisfaction': self.satisfaction_var.get()
}
}
# 保存到数据库
if self._save_to_database():
messagebox.showinfo("成功", "所有信息保存成功!")
self.dialog.destroy()
else:
messagebox.showerror("错误", "保存失败,请重试")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {str(e)}")
def _save_to_database(self) -> bool:
"""保存到数据库"""
try:
from modules.data_collection import collect_questionnaire_data, record_meal
# 保存基础信息
basic_data = self.input_data['basic_info']
age_mapping = {
"18-24岁": 21, "25-30岁": 27, "31-35岁": 33, "36-40岁": 38,
"41-45岁": 43, "46-50岁": 48, "51-55岁": 53, "56-60岁": 58, "60岁以上": 65
}
height_mapping = {
"150cm以下": 150, "150-155cm": 152, "155-160cm": 157, "160-165cm": 162,
"165-170cm": 167, "170-175cm": 172, "175-180cm": 177, "180cm以上": 180
}
weight_mapping = {
"40kg以下": 40, "40-45kg": 42, "45-50kg": 47, "50-55kg": 52,
"55-60kg": 57, "60-65kg": 62, "65-70kg": 67, "70-75kg": 72,
"75-80kg": 77, "80kg以上": 80
}
activity_mapping = {
"久坐": "sedentary", "轻度活动": "light", "中等": "moderate",
"高度活动": "high", "极度活动": "very_high"
}
basic_answers = {
'name': basic_data['name'],
'age': age_mapping.get(basic_data['age_range'], 25),
'gender': basic_data['gender'],
'height': height_mapping.get(basic_data['height_range'], 165),
'weight': weight_mapping.get(basic_data['weight_range'], 55),
'activity_level': activity_mapping.get(basic_data['activity_level'], 'moderate')
}
collect_questionnaire_data(self.user_id, 'basic', basic_answers)
# 保存口味偏好
preferences_data = self.input_data['preferences']
taste_answers = {
'taste_preference': preferences_data['taste'],
'diet_type': preferences_data['diet_type'],
'allergies': [preferences_data['allergies']] if preferences_data['allergies'] != "" else [],
'dislikes': [preferences_data['dislikes']] if preferences_data['dislikes'] != "" else []
}
collect_questionnaire_data(self.user_id, 'taste', taste_answers)
# 保存健康目标
health_data = self.input_data['health_goals']
health_answers = {
'main_goal': health_data['main_goal'],
'sub_goal': health_data['sub_goal']
}
collect_questionnaire_data(self.user_id, 'health', health_answers)
# 保存快速餐食记录
meal_data = self.input_data['quick_meal']
meal_type_mapping = {
"早餐": "breakfast", "午餐": "lunch", "晚餐": "dinner", "加餐": "snack"
}
# 解析食物组合
food_combo = meal_data['food_combo']
if "+" in food_combo:
foods = [food.strip() for food in food_combo.split("+")]
else:
foods = [food_combo]
# 估算分量
portion_mapping = {
"少量": "1小份", "正常": "1份", "较多": "1大份", "很多": "2份"
}
quantities = [portion_mapping.get(meal_data['portion'], "1份")] * len(foods)
meal_record = {
'date': datetime.now().strftime('%Y-%m-%d'),
'meal_type': meal_type_mapping.get(meal_data['meal_type'], 'lunch'),
'foods': foods,
'quantities': quantities,
'satisfaction_score': meal_data['satisfaction']
}
record_meal(self.user_id, meal_record)
return True
except Exception as e:
print(f"保存到数据库失败: {e}")
return False
def _cancel(self):
"""取消录入"""
self.dialog.destroy()
# 便捷函数
def show_quick_user_input_dialog(parent, user_id: str):
"""显示快速用户需求录入对话框"""
dialog = QuickUserInputDialog(parent, user_id)
parent.wait_window(dialog.dialog)
if __name__ == "__main__":
# 测试快速录入对话框
root = tk.Tk()
root.title("测试快速录入")
def test_dialog():
show_quick_user_input_dialog(root, "test_user")
test_button = tk.Button(root, text="测试快速录入", command=test_dialog)
test_button.pack(pady=20)
root.mainloop()