已点菜品栏更新
This commit is contained in:
parent
93fb61e140
commit
1bef9631eb
|
|
@ -144,6 +144,24 @@ class MealScheduleStore:
|
||||||
return False
|
return False
|
||||||
return name in self.today_dish_names()
|
return name in self.today_dish_names()
|
||||||
|
|
||||||
|
def lookup_price(self, dish_name):
|
||||||
|
"""按菜名查今日排餐价格;未命中返回 None。"""
|
||||||
|
name = (dish_name or '').strip()
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
today = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
with self._lock:
|
||||||
|
raw = self._raw
|
||||||
|
date = self._date
|
||||||
|
if not raw or date != today:
|
||||||
|
return None
|
||||||
|
for base in raw.get('bases') or []:
|
||||||
|
for meal in base.get('meals') or []:
|
||||||
|
for dish in meal.get('dishes') or []:
|
||||||
|
if (dish.get('name') or '').strip() == name:
|
||||||
|
return dish.get('price')
|
||||||
|
return None
|
||||||
|
|
||||||
def current_period(self, now=None):
|
def current_period(self, now=None):
|
||||||
"""
|
"""
|
||||||
根据当前时间匹配餐段。
|
根据当前时间匹配餐段。
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._order_locked = False # 报过一单后锁定,清场后才允许下一单
|
self._order_locked = False # 报过一单后锁定,清场后才允许下一单
|
||||||
self._order_clear_since = None # 无盘连续计时起点
|
self._order_clear_since = None # 无盘连续计时起点
|
||||||
self._order_clear_hold = 1.5 # 无盘持续多久才解锁(秒)
|
self._order_clear_hold = 1.5 # 无盘持续多久才解锁(秒)
|
||||||
|
self._cart_hold = False # 本盘清单已展示:清场前不刷新
|
||||||
self._last_detect_fp = None # 上一帧指纹(静止画面跳过检测)
|
self._last_detect_fp = None # 上一帧指纹(静止画面跳过检测)
|
||||||
self._last_live_dishes = None # 上一轮识别结果(跳过检测时推进稳定计时)
|
self._last_live_dishes = None # 上一轮识别结果(跳过检测时推进稳定计时)
|
||||||
self._frame_sim_threshold = 0.992 # 帧相似度阈值:非常相似则跳过 YOLO/分类
|
self._frame_sim_threshold = 0.992 # 帧相似度阈值:非常相似则跳过 YOLO/分类
|
||||||
|
|
@ -304,11 +305,15 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
|
|
||||||
def _on_wheel_all(self, event):
|
def _on_wheel_all(self, event):
|
||||||
alive = []
|
alive = []
|
||||||
|
menu_cv = getattr(self, "_menu_canvas", None)
|
||||||
for canvas in list(getattr(self, "_scroll_canvases", [])):
|
for canvas in list(getattr(self, "_scroll_canvases", [])):
|
||||||
try:
|
try:
|
||||||
if not canvas.winfo_exists():
|
if not canvas.winfo_exists():
|
||||||
continue
|
continue
|
||||||
alive.append(canvas)
|
alive.append(canvas)
|
||||||
|
# 今日菜单为空时禁止滚动
|
||||||
|
if menu_cv is not None and canvas is menu_cv and not getattr(self, "_menu_scroll_enabled", True):
|
||||||
|
continue
|
||||||
x, y = canvas.winfo_rootx(), canvas.winfo_rooty()
|
x, y = canvas.winfo_rootx(), canvas.winfo_rooty()
|
||||||
if x <= event.x_root <= x + canvas.winfo_width() and \
|
if x <= event.x_root <= x + canvas.winfo_width() and \
|
||||||
y <= event.y_root <= y + canvas.winfo_height():
|
y <= event.y_root <= y + canvas.winfo_height():
|
||||||
|
|
@ -1297,38 +1302,120 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._sync_dialog = None
|
self._sync_dialog = None
|
||||||
tk.Frame(self.root, bg=c["border"], height=1).pack(fill=tk.X)
|
tk.Frame(self.root, bg=c["border"], height=1).pack(fill=tk.X)
|
||||||
|
|
||||||
def _build_today_menu(self, parent, c):
|
def _build_left_panel(self, parent, c):
|
||||||
"""左侧:第三方接口拉取的今日排餐菜单。"""
|
"""左侧大框:已点菜品 / 今日菜单 同级页签切换。"""
|
||||||
panel = tk.Frame(
|
shell = tk.Frame(
|
||||||
parent, bg=c["surface"],
|
parent, bg=c["surface"],
|
||||||
highlightbackground=c["border"], highlightthickness=1,
|
highlightbackground=c["border"], highlightthickness=1,
|
||||||
)
|
)
|
||||||
panel.pack(fill=tk.X, pady=(0, 8))
|
shell.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
head = tk.Frame(panel, bg=c["surface"])
|
tab_row = tk.Frame(shell, bg=c["surface"])
|
||||||
head.pack(fill=tk.X, padx=12, pady=(10, 4))
|
tab_row.pack(fill=tk.X, padx=10, pady=(10, 6))
|
||||||
tk.Label(
|
|
||||||
head, text="今日菜单", bg=c["surface"], fg=c["text"], font=FONTS["h1"],
|
tabs = tk.Frame(tab_row, bg=c["surface"])
|
||||||
).pack(side=tk.LEFT)
|
tabs.pack(side=tk.LEFT)
|
||||||
PillButton(
|
self._left_tab = "cart"
|
||||||
head, text="刷新", variant="ghost", size="sm",
|
self._left_tab_btns = {}
|
||||||
|
self._left_tab_cart_btn = PillButton(
|
||||||
|
tabs, text="已点菜品", variant="primary", size="sm",
|
||||||
|
command=lambda: self._switch_left_tab("cart"),
|
||||||
|
)
|
||||||
|
self._left_tab_cart_btn.pack(side=tk.LEFT, padx=(0, 6))
|
||||||
|
self._left_tab_menu_btn = PillButton(
|
||||||
|
tabs, text="今日菜单", variant="ghost", size="sm",
|
||||||
|
command=lambda: self._switch_left_tab("menu"),
|
||||||
|
)
|
||||||
|
self._left_tab_menu_btn.pack(side=tk.LEFT)
|
||||||
|
self._left_tab_btns = {
|
||||||
|
"cart": self._left_tab_cart_btn,
|
||||||
|
"menu": self._left_tab_menu_btn,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 右侧:随页签切换的操作区
|
||||||
|
self._left_tab_actions = tk.Frame(tab_row, bg=c["surface"])
|
||||||
|
self._left_tab_actions.pack(side=tk.RIGHT)
|
||||||
|
self.cart_count_label = tk.Label(
|
||||||
|
self._left_tab_actions, text="0 项", bg=c["surface"], fg=c["muted"],
|
||||||
|
font=FONTS["small"],
|
||||||
|
)
|
||||||
|
self._menu_refresh_btn = PillButton(
|
||||||
|
self._left_tab_actions, text="刷新", variant="ghost", size="sm",
|
||||||
command=self.refresh_today_menu_async,
|
command=self.refresh_today_menu_async,
|
||||||
tooltip="重新从管理系统拉取今日排餐",
|
tooltip="重新从管理系统拉取今日排餐",
|
||||||
).pack(side=tk.RIGHT)
|
)
|
||||||
|
|
||||||
|
body = tk.Frame(shell, bg=c["surface"])
|
||||||
|
body.pack(fill=tk.BOTH, expand=True)
|
||||||
|
self._left_body = body
|
||||||
|
|
||||||
|
self._cart_page = tk.Frame(body, bg=c["surface"])
|
||||||
|
self._menu_page = tk.Frame(body, bg=c["surface"])
|
||||||
|
self._build_pos_cart(self._cart_page, c)
|
||||||
|
self._build_today_menu(self._menu_page, c)
|
||||||
|
self._switch_left_tab("cart")
|
||||||
|
return shell
|
||||||
|
|
||||||
|
def _switch_left_tab(self, which):
|
||||||
|
"""切换左侧页签:cart=已点菜品,menu=今日菜单。"""
|
||||||
|
which = "menu" if which == "menu" else "cart"
|
||||||
|
self._left_tab = which
|
||||||
|
cart_page = getattr(self, "_cart_page", None)
|
||||||
|
menu_page = getattr(self, "_menu_page", None)
|
||||||
|
if cart_page is None or menu_page is None:
|
||||||
|
return
|
||||||
|
if which == "cart":
|
||||||
|
menu_page.pack_forget()
|
||||||
|
cart_page.pack(fill=tk.BOTH, expand=True)
|
||||||
|
else:
|
||||||
|
cart_page.pack_forget()
|
||||||
|
menu_page.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
for key, btn in (getattr(self, "_left_tab_btns", {}) or {}).items():
|
||||||
|
try:
|
||||||
|
btn.set_variant("primary" if key == which else "ghost")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
btn.configure(variant="primary" if key == which else "ghost")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 操作区:已点显示数量,菜单显示刷新
|
||||||
|
try:
|
||||||
|
self.cart_count_label.pack_forget()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._menu_refresh_btn.pack_forget()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if which == "cart":
|
||||||
|
try:
|
||||||
|
self.cart_count_label.pack(side=tk.RIGHT)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self._menu_refresh_btn.pack(side=tk.RIGHT)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _build_today_menu(self, parent, c):
|
||||||
|
"""今日排餐内容(嵌入左侧页签页,不再单独外框)。"""
|
||||||
|
panel = parent
|
||||||
|
|
||||||
self.menu_meta_label = tk.Label(
|
self.menu_meta_label = tk.Label(
|
||||||
panel, text="正在加载今日菜单…", bg=c["surface"], fg=c["muted"],
|
panel, text="正在加载今日菜单…", bg=c["surface"], fg=c["muted"],
|
||||||
font=FONTS["small"], anchor="w",
|
font=FONTS["small"], anchor="w",
|
||||||
)
|
)
|
||||||
self.menu_meta_label.pack(fill=tk.X, padx=12, pady=(0, 4))
|
self.menu_meta_label.pack(fill=tk.X, padx=12, pady=(4, 4))
|
||||||
|
|
||||||
# 餐段切换
|
# 餐段切换
|
||||||
self.menu_period_bar = tk.Frame(panel, bg=c["surface"])
|
self.menu_period_bar = tk.Frame(panel, bg=c["surface"])
|
||||||
self.menu_period_bar.pack(fill=tk.X, padx=8, pady=(0, 4))
|
self.menu_period_bar.pack(fill=tk.X, padx=8, pady=(0, 4))
|
||||||
|
|
||||||
list_host = tk.Frame(panel, bg=c["surface"], height=148)
|
list_host = tk.Frame(panel, bg=c["surface"])
|
||||||
list_host.pack(fill=tk.X, padx=4, pady=(0, 8))
|
list_host.pack(fill=tk.BOTH, expand=True, padx=4, pady=(0, 8))
|
||||||
list_host.pack_propagate(False)
|
|
||||||
wrap, self.menu_dish_frame, self._menu_canvas = self._make_scroll_area(
|
wrap, self.menu_dish_frame, self._menu_canvas = self._make_scroll_area(
|
||||||
list_host, c["surface"],
|
list_host, c["surface"],
|
||||||
)
|
)
|
||||||
|
|
@ -1338,6 +1425,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._menu_selected_period = None # None=自动当前餐段
|
self._menu_selected_period = None # None=自动当前餐段
|
||||||
self._menu_period_btns = []
|
self._menu_period_btns = []
|
||||||
self._menu_refresh_job = None
|
self._menu_refresh_job = None
|
||||||
|
self._menu_scroll_enabled = False
|
||||||
return panel
|
return panel
|
||||||
|
|
||||||
def refresh_today_menu_async(self):
|
def refresh_today_menu_async(self):
|
||||||
|
|
@ -1442,7 +1530,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
if not periods:
|
if not periods:
|
||||||
EmptyState(
|
EmptyState(
|
||||||
self.menu_dish_frame, text="暂无今日菜单",
|
self.menu_dish_frame, text="暂无今日菜单",
|
||||||
hint="启动后会自动从管理系统拉取;也可点右上角「刷新」",
|
hint="启动后会自动从管理系统拉取;也可点页签旁「刷新」",
|
||||||
glyph="◎", bg=c["surface"], pady=16,
|
glyph="◎", bg=c["surface"], pady=16,
|
||||||
).pack(fill=tk.X)
|
).pack(fill=tk.X)
|
||||||
elif not dishes:
|
elif not dishes:
|
||||||
|
|
@ -1478,16 +1566,24 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
row, text=name, bg=bg, fg=c["text"], font=FONTS["ui"],
|
row, text=name, bg=bg, fg=c["text"], font=FONTS["ui"],
|
||||||
anchor="w",
|
anchor="w",
|
||||||
).pack(side=tk.LEFT, padx=(8, 4), pady=4)
|
).pack(side=tk.LEFT, padx=(8, 4), pady=4)
|
||||||
if price_txt:
|
|
||||||
tk.Label(
|
tk.Label(
|
||||||
row, text=price_txt, bg=bg, fg=c["muted"], font=FONTS["small"],
|
row, text=price_txt or "—", bg=bg,
|
||||||
|
fg=c["text"] if price_txt else c["muted"],
|
||||||
|
font=FONTS["small"],
|
||||||
).pack(side=tk.RIGHT, padx=8)
|
).pack(side=tk.RIGHT, padx=8)
|
||||||
|
|
||||||
|
# 有菜才允许滚动;空菜单锁死滚动区域,避免空转
|
||||||
try:
|
try:
|
||||||
self._menu_canvas.update_idletasks()
|
self._menu_canvas.update_idletasks()
|
||||||
|
if dishes:
|
||||||
self._menu_canvas.configure(scrollregion=self._menu_canvas.bbox("all"))
|
self._menu_canvas.configure(scrollregion=self._menu_canvas.bbox("all"))
|
||||||
|
self._menu_scroll_enabled = True
|
||||||
|
else:
|
||||||
|
self._menu_canvas.yview_moveto(0)
|
||||||
|
self._menu_canvas.configure(scrollregion=(0, 0, 0, 0))
|
||||||
|
self._menu_scroll_enabled = False
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
self._menu_scroll_enabled = bool(dishes)
|
||||||
|
|
||||||
# 每分钟刷新一次(餐段切换 / 元信息)
|
# 每分钟刷新一次(餐段切换 / 元信息)
|
||||||
job = getattr(self, "_menu_refresh_job", None)
|
job = getattr(self, "_menu_refresh_job", None)
|
||||||
|
|
@ -1506,25 +1602,25 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self.refresh_today_menu()
|
self.refresh_today_menu()
|
||||||
|
|
||||||
def _build_pos_cart(self, parent, c):
|
def _build_pos_cart(self, parent, c):
|
||||||
panel = tk.Frame(parent, bg=c["surface"], highlightbackground=c["border"], highlightthickness=1)
|
"""已点菜品内容(嵌入左侧页签页,标题由页签承担)。"""
|
||||||
panel.pack(fill=tk.BOTH, expand=True)
|
panel = parent
|
||||||
|
|
||||||
head = tk.Frame(panel, bg=c["surface"])
|
|
||||||
head.pack(fill=tk.X, padx=12, pady=(12, 8))
|
|
||||||
tk.Label(head, text="已点菜品", bg=c["surface"], fg=c["text"],
|
|
||||||
font=FONTS["h1"]).pack(side=tk.LEFT)
|
|
||||||
self.cart_count_label = tk.Label(head, text="0 项", bg=c["surface"], fg=c["muted"],
|
|
||||||
font=FONTS["small"])
|
|
||||||
self.cart_count_label.pack(side=tk.RIGHT)
|
|
||||||
|
|
||||||
cols = tk.Frame(panel, bg=c["pos_row_alt"])
|
cols = tk.Frame(panel, bg=c["pos_row_alt"])
|
||||||
cols.pack(fill=tk.X)
|
cols.pack(fill=tk.X)
|
||||||
for text, w in (("菜品", 22), ("数量", 10), ("", 4)):
|
for text, w in (("菜品", 14), ("金额", 8), ("数量", 8), ("", 4)):
|
||||||
tk.Label(cols, text=text, bg=c["pos_row_alt"], fg=c["muted"],
|
tk.Label(cols, text=text, bg=c["pos_row_alt"], fg=c["muted"],
|
||||||
font=FONTS["small"], width=w, anchor="w").pack(side=tk.LEFT, padx=4, pady=6)
|
font=FONTS["small"], width=w, anchor="w").pack(side=tk.LEFT, padx=4, pady=6)
|
||||||
|
|
||||||
wrap, self.cart_frame, self._cart_canvas = self._make_scroll_area(panel, c["surface"])
|
wrap, self.cart_frame, self._cart_canvas = self._make_scroll_area(panel, c["surface"])
|
||||||
wrap.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
|
wrap.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
|
||||||
|
|
||||||
|
foot = tk.Frame(panel, bg=c["surface"])
|
||||||
|
foot.pack(fill=tk.X, padx=12, pady=(0, 10))
|
||||||
|
self.cart_total_label = tk.Label(
|
||||||
|
foot, text="合计 ¥0", bg=c["surface"], fg=c["text"],
|
||||||
|
font=FONTS["ui_bold"], anchor="e",
|
||||||
|
)
|
||||||
|
self.cart_total_label.pack(side=tk.RIGHT)
|
||||||
return panel
|
return panel
|
||||||
|
|
||||||
def _build_pos_preview(self, parent, c):
|
def _build_pos_preview(self, parent, c):
|
||||||
|
|
@ -1696,8 +1792,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
left = tk.Frame(body, bg=c["bg"], width=SIZES.get("cart_w", 320))
|
left = tk.Frame(body, bg=c["bg"], width=SIZES.get("cart_w", 320))
|
||||||
left.grid(row=0, column=0, sticky="nsew", padx=(0, 8))
|
left.grid(row=0, column=0, sticky="nsew", padx=(0, 8))
|
||||||
left.grid_propagate(False)
|
left.grid_propagate(False)
|
||||||
self._build_today_menu(left, c)
|
self._build_left_panel(left, c)
|
||||||
self._build_pos_cart(left, c)
|
|
||||||
|
|
||||||
right = tk.Frame(body, bg=c["bg"])
|
right = tk.Frame(body, bg=c["bg"])
|
||||||
right.grid(row=0, column=1, sticky="nsew")
|
right.grid(row=0, column=1, sticky="nsew")
|
||||||
|
|
@ -1975,16 +2070,15 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._order_locked = False
|
self._order_locked = False
|
||||||
self._order_clear_since = None
|
self._order_clear_since = None
|
||||||
self._last_reported_sig = None
|
self._last_reported_sig = None
|
||||||
|
self._cart_hold = False
|
||||||
|
|
||||||
def _note_order_scene_busy(self):
|
def _note_order_scene_busy(self):
|
||||||
"""画面上仍有托盘/已识别菜:清场计时清零。"""
|
"""画面上仍有托盘/已识别菜:清场计时清零。"""
|
||||||
self._order_clear_since = None
|
self._order_clear_since = None
|
||||||
|
|
||||||
def _note_order_scene_clear(self):
|
def _note_order_scene_clear(self):
|
||||||
"""无餐盘:累计清场时间,满 hold 后解除订单锁定。"""
|
"""无餐盘:累计清场时间,满 hold 后解除订单锁定,并允许下一盘刷新清单。"""
|
||||||
if not self._order_locked:
|
# 无上报锁定时也要能「松开清单」:无盘持续后允许下一盘覆盖已点菜品
|
||||||
self._order_clear_since = None
|
|
||||||
return False
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if self._order_clear_since is None:
|
if self._order_clear_since is None:
|
||||||
self._order_clear_since = now
|
self._order_clear_since = now
|
||||||
|
|
@ -1992,10 +2086,15 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
hold = float(getattr(self, "_order_clear_hold", 1.5) or 1.5)
|
hold = float(getattr(self, "_order_clear_hold", 1.5) or 1.5)
|
||||||
if now - self._order_clear_since < hold:
|
if now - self._order_clear_since < hold:
|
||||||
return False
|
return False
|
||||||
|
was_locked = bool(self._order_locked)
|
||||||
self._order_locked = False
|
self._order_locked = False
|
||||||
self._order_clear_since = None
|
self._order_clear_since = None
|
||||||
self._last_reported_sig = None
|
self._last_reported_sig = None
|
||||||
|
self._cart_hold = False # 下一盘识别到目标时可更换清单
|
||||||
|
if was_locked:
|
||||||
print(f"[order] 清场解锁(无盘持续 ≥ {hold:g}s,允许下一单)")
|
print(f"[order] 清场解锁(无盘持续 ≥ {hold:g}s,允许下一单)")
|
||||||
|
else:
|
||||||
|
print(f"[order] 清场,已点清单可被下一盘覆盖(无盘 ≥ {hold:g}s)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _try_live_report(self, save_path, known_dishes, preview):
|
def _try_live_report(self, save_path, known_dishes, preview):
|
||||||
|
|
@ -2451,15 +2550,15 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
|
|
||||||
def _no_plate(did_unlock=unlocked):
|
def _no_plate(did_unlock=unlocked):
|
||||||
self._live_overlay_dishes = []
|
self._live_overlay_dishes = []
|
||||||
self.render_result_dock([], None)
|
# 无盘不刷空清单:保留上一盘已点菜品,直到下一盘识别到目标
|
||||||
tip = "扫描中:未检测到餐盘"
|
tip = "扫描中:未检测到餐盘|左侧保留上一盘"
|
||||||
if did_unlock:
|
if did_unlock:
|
||||||
tip += "|已清场,可报下一单"
|
tip += "|已清场,可报下一单"
|
||||||
self._set_result_text(tip, "warn")
|
self._set_result_text(tip, "warn")
|
||||||
if hasattr(self, "scan_tip_label"):
|
if hasattr(self, "scan_tip_label"):
|
||||||
try:
|
try:
|
||||||
self.scan_tip_label.config(
|
self.scan_tip_label.config(
|
||||||
text="扫描中 · 请将餐盘放入画面中央"
|
text="扫描中 · 请放入下一盘(已点清单暂留)"
|
||||||
+ (" · 已解锁下一单" if did_unlock else "")
|
+ (" · 已解锁下一单" if did_unlock else "")
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -2518,9 +2617,9 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
written=pending_written, elapsed=stable_elapsed,
|
written=pending_written, elapsed=stable_elapsed,
|
||||||
committed=already_committed, hold_s=hold,
|
committed=already_committed, hold_s=hold,
|
||||||
):
|
):
|
||||||
# 只更新叠加框 + 左侧清单;预览继续刷实时帧
|
# 只更新叠加框;清单:本盘展示后冻结,清场后下一盘再换
|
||||||
self._live_overlay_dishes = list(ds or [])
|
self._live_overlay_dishes = list(ds or [])
|
||||||
self.render_result_dock(ds, path)
|
self.render_result_dock(ds, path, live_hold=True)
|
||||||
try:
|
try:
|
||||||
self.update_pending_btn()
|
self.update_pending_btn()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -2529,13 +2628,14 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
known = [d for d in ds if not d.get("is_unknown")]
|
known = [d for d in ds if not d.get("is_unknown")]
|
||||||
unknown = [d for d in ds if d.get("is_unknown")]
|
unknown = [d for d in ds if d.get("is_unknown")]
|
||||||
|
|
||||||
|
if known or unknown:
|
||||||
|
msg = f"扫描中|清单 {len(ds)} 项"
|
||||||
if known:
|
if known:
|
||||||
top = max(known, key=lambda d: d.get("score") or 0)
|
top = max(known, key=lambda d: d.get("score") or 0)
|
||||||
name = top.get("label") or "菜品"
|
msg += f"|{top.get('label') or '菜品'}"
|
||||||
msg = f"扫描中|已识别 {len(known)}|{name}"
|
|
||||||
if unknown:
|
if unknown:
|
||||||
msg += f"|未知 {len(unknown)}(不入清单)"
|
msg += f"|未知 {len(unknown)}"
|
||||||
self._set_result_text(msg, "ok" if not unknown else "warn")
|
self._set_result_text(msg, "ok" if known else "warn")
|
||||||
else:
|
else:
|
||||||
self._set_result_text(
|
self._set_result_text(
|
||||||
txt or f"扫描中|检出 {len(ds)} 盘,尚无达到阈值的菜品",
|
txt or f"扫描中|检出 {len(ds)} 盘,尚无达到阈值的菜品",
|
||||||
|
|
@ -2571,7 +2671,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.scan_tip_label.config(
|
self.scan_tip_label.config(
|
||||||
text="实时扫描中 · 仅已识别菜计入左侧清单"
|
text="实时扫描中 · 下一盘识别后刷新已点清单"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -2590,6 +2690,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._cart = []
|
self._cart = []
|
||||||
self._last_dishes = []
|
self._last_dishes = []
|
||||||
self._live_overlay_dishes = []
|
self._live_overlay_dishes = []
|
||||||
|
self._cart_hold = False
|
||||||
self.render_cart()
|
self.render_cart()
|
||||||
if not self._preview_enabled and not self._live_enabled and hasattr(self, "img_label"):
|
if not self._preview_enabled and not self._live_enabled and hasattr(self, "img_label"):
|
||||||
self.img_label.config(
|
self.img_label.config(
|
||||||
|
|
@ -2599,23 +2700,40 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self.img_label.image = None
|
self.img_label.image = None
|
||||||
self._set_result_text("已清空本次已点菜品", "ok")
|
self._set_result_text("已清空本次已点菜品", "ok")
|
||||||
|
|
||||||
|
def _format_price_text(self, price):
|
||||||
|
if isinstance(price, (int, float)):
|
||||||
|
return f"¥{price:g}"
|
||||||
|
if price not in (None, ""):
|
||||||
|
return f"¥{price}"
|
||||||
|
return "—"
|
||||||
|
|
||||||
def fill_cart_from_dishes(self, dishes):
|
def fill_cart_from_dishes(self, dishes):
|
||||||
"""左侧清单只放已识别菜;未知仅留在预览标注 / 待学习流程。"""
|
"""写入左侧清单:已识别 + 未知;金额按今日菜单价查询。"""
|
||||||
cart = []
|
cart = []
|
||||||
for d in dishes or []:
|
for d in dishes or []:
|
||||||
if d.get("is_unknown"):
|
is_unk = bool(d.get("is_unknown"))
|
||||||
continue
|
if is_unk:
|
||||||
|
sug = (d.get("suggested_category") or "").strip()
|
||||||
|
name = f"未知·{sug}" if sug else "未知"
|
||||||
|
price = None
|
||||||
|
else:
|
||||||
name = d.get("label") or d.get("name") or "菜品"
|
name = d.get("label") or d.get("name") or "菜品"
|
||||||
|
try:
|
||||||
|
price = self.meal_schedule.lookup_price(name)
|
||||||
|
except Exception:
|
||||||
|
price = None
|
||||||
cart.append({
|
cart.append({
|
||||||
"index": d.get("index"),
|
"index": d.get("index"),
|
||||||
"name": name,
|
"name": name,
|
||||||
"label": d.get("label"),
|
"label": d.get("label"),
|
||||||
"score": d.get("score"),
|
"score": d.get("score"),
|
||||||
"is_unknown": False,
|
"is_unknown": is_unk,
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
|
"price": price,
|
||||||
"crop_image": d.get("crop_image"),
|
"crop_image": d.get("crop_image"),
|
||||||
"pending_id": d.get("pending_id"),
|
"pending_id": d.get("pending_id"),
|
||||||
"suggested_category": d.get("suggested_category"),
|
"suggested_category": d.get("suggested_category"),
|
||||||
|
"in_today_menu": bool(d.get("in_today_menu")),
|
||||||
})
|
})
|
||||||
self._cart = cart
|
self._cart = cart
|
||||||
self._last_dishes = dishes or []
|
self._last_dishes = dishes or []
|
||||||
|
|
@ -2641,10 +2759,9 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._cart_thumbs = []
|
self._cart_thumbs = []
|
||||||
|
|
||||||
if not self._cart:
|
if not self._cart:
|
||||||
hold_s = float(getattr(self, "_live_stable_seconds", DISH_LIVE_PENDING_STABLE_SECONDS) or 1.0)
|
|
||||||
EmptyState(
|
EmptyState(
|
||||||
self.cart_frame, text="暂无已识别菜品",
|
self.cart_frame, text="暂无已点菜品",
|
||||||
hint=f"未知菜不入清单;画面稳定约 {hold_s:g} 秒后记入待学习",
|
hint="下一盘出现并识别到目标后刷新;未知菜也会显示",
|
||||||
glyph="◍", bg=c["surface"], pady=28,
|
glyph="◍", bg=c["surface"], pady=28,
|
||||||
).pack(fill=tk.X)
|
).pack(fill=tk.X)
|
||||||
else:
|
else:
|
||||||
|
|
@ -2669,7 +2786,8 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
|
|
||||||
info = tk.Frame(pad, bg=bg)
|
info = tk.Frame(pad, bg=bg)
|
||||||
info.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
info.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||||
tk.Label(info, text=item.get("name") or "菜品", bg=bg, fg=c["text"],
|
name_fg = c["warn"] if item.get("is_unknown") else c["text"]
|
||||||
|
tk.Label(info, text=item.get("name") or "菜品", bg=bg, fg=name_fg,
|
||||||
font=FONTS["ui_bold"], anchor="w").pack(fill=tk.X)
|
font=FONTS["ui_bold"], anchor="w").pack(fill=tk.X)
|
||||||
score = item.get("score")
|
score = item.get("score")
|
||||||
meta = f"#{item.get('index') or i + 1}"
|
meta = f"#{item.get('index') or i + 1}"
|
||||||
|
|
@ -2677,9 +2795,18 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
meta += f" · {score:.2f}"
|
meta += f" · {score:.2f}"
|
||||||
if item.get("is_unknown"):
|
if item.get("is_unknown"):
|
||||||
meta += " · 待确认"
|
meta += " · 待确认"
|
||||||
|
elif item.get("in_today_menu"):
|
||||||
|
meta += " · 今日菜单"
|
||||||
tk.Label(info, text=meta, bg=bg, fg=c["muted"],
|
tk.Label(info, text=meta, bg=bg, fg=c["muted"],
|
||||||
font=FONTS["small"], anchor="w").pack(fill=tk.X)
|
font=FONTS["small"], anchor="w").pack(fill=tk.X)
|
||||||
|
|
||||||
|
price_txt = self._format_price_text(item.get("price"))
|
||||||
|
tk.Label(
|
||||||
|
pad, text=price_txt, bg=bg,
|
||||||
|
fg=c["text"] if price_txt != "—" else c["muted"],
|
||||||
|
font=FONTS["ui"], width=6, anchor="e",
|
||||||
|
).pack(side=tk.LEFT, padx=(4, 2))
|
||||||
|
|
||||||
qty_box = tk.Frame(pad, bg=bg)
|
qty_box = tk.Frame(pad, bg=bg)
|
||||||
qty_box.pack(side=tk.LEFT, padx=6)
|
qty_box.pack(side=tk.LEFT, padx=6)
|
||||||
PillButton(qty_box, text="−", variant="ghost", size="sm",
|
PillButton(qty_box, text="−", variant="ghost", size="sm",
|
||||||
|
|
@ -2693,11 +2820,45 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
|
|
||||||
if hasattr(self, "cart_count_label"):
|
if hasattr(self, "cart_count_label"):
|
||||||
self.cart_count_label.config(text=f"{len(self._cart)} 项")
|
self.cart_count_label.config(text=f"{len(self._cart)} 项")
|
||||||
|
if hasattr(self, "cart_total_label"):
|
||||||
|
total = 0.0
|
||||||
|
has_price = False
|
||||||
|
for item in self._cart:
|
||||||
|
p = item.get("price")
|
||||||
|
q = int(item.get("qty") or 1)
|
||||||
|
if isinstance(p, (int, float)):
|
||||||
|
total += float(p) * q
|
||||||
|
has_price = True
|
||||||
|
self.cart_total_label.config(
|
||||||
|
text=f"合计 ¥{total:g}" if has_price else "合计 —"
|
||||||
|
)
|
||||||
|
|
||||||
def render_result_dock(self, dishes, labeled_path=None):
|
def render_result_dock(self, dishes, labeled_path=None, *, live_hold=False):
|
||||||
"""兼容旧调用:识别结果写入购物车。"""
|
"""识别结果写入购物车。
|
||||||
|
|
||||||
|
live_hold=True(实时扫描):
|
||||||
|
- 无目标:不改清单
|
||||||
|
- 本盘已展示过(_cart_hold):不因标签抖动刷新
|
||||||
|
- 清场后下一盘识别到目标:才更换清单
|
||||||
|
"""
|
||||||
|
if live_hold:
|
||||||
|
if not dishes:
|
||||||
|
return
|
||||||
|
if getattr(self, "_cart_hold", False):
|
||||||
|
return
|
||||||
self.fill_cart_from_dishes(dishes)
|
self.fill_cart_from_dishes(dishes)
|
||||||
self.render_cart()
|
self.render_cart()
|
||||||
|
self._cart_hold = True
|
||||||
|
# 新一盘写入清单时自动切到「已点菜品」
|
||||||
|
try:
|
||||||
|
if getattr(self, "_left_tab", None) != "cart":
|
||||||
|
self._switch_left_tab("cart")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
self.fill_cart_from_dishes(dishes)
|
||||||
|
self.render_cart()
|
||||||
|
self._cart_hold = False
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 模板详情 / 删除
|
# 模板详情 / 删除
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue