From fcc43390c614fb6eb550d2aa3c82361b1a318dd0 Mon Sep 17 00:00:00 2001 From: LiXin01 <1625205083@qq.com> Date: Fri, 16 Oct 2026 11:19:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=85=E5=AD=A6=E4=B9=A0=E5=BA=93ui=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/pending_learning_ui.py | 121 +++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 20 deletions(-) diff --git a/ui/pending_learning_ui.py b/ui/pending_learning_ui.py index 5c173d3..d7e006a 100644 --- a/ui/pending_learning_ui.py +++ b/ui/pending_learning_ui.py @@ -170,10 +170,10 @@ class PendingLearningUIMixin: values.append(name) seen.add(name) suggested = (suggested or "").strip() - if suggested and suggested in values: - initial = suggested - else: - initial = "" + # 最接近菜名即使不在今日菜单/主食里,也填进输入框并放到下拉最前 + if suggested and suggested not in seen: + values.insert(0, suggested) + initial = suggested return period_name, period_names, values, initial, is_period def _today_menu_info(self): @@ -529,8 +529,11 @@ class PendingLearningUIMixin: header.pack(fill=tk.X, padx=16, pady=(14, 6)) tk.Label(header, text="待学习菜品库", bg=COLORS["bg"], fg=COLORS["text"], font=FONTS["h1"]).pack(side=tk.LEFT) - tk.Label(header, text=f"共 {len(items)} 条记录", bg=COLORS["bg"], - fg=COLORS["muted"], font=FONTS["small"]).pack(side=tk.LEFT, padx=10) + count_lbl = tk.Label(header, text=f"共 {len(items)} 条记录", bg=COLORS["bg"], + fg=COLORS["muted"], font=FONTS["small"]) + count_lbl.pack(side=tk.LEFT, padx=10) + dialog._pending_count_label = count_lbl + dialog._pending_items = items if items: PillButton(header, text="全部忽略", variant="ghost", size="sm", command=lambda: self._ignore_all_pending(items, dialog), @@ -642,7 +645,9 @@ class PendingLearningUIMixin: actions.pack(side=tk.RIGHT, padx=(10, 0)) pid = item["id"] PillButton(actions, text="学习", variant="primary", size="sm", - command=lambda p=pid, s=suggested: self._learn_pending_from_dialog(p, s, dialog), + command=lambda p=pid, s=suggested, row=box: self._learn_pending_from_dialog( + p, s, dialog, row=row, + ), ).pack(side=tk.LEFT, padx=2) PillButton(actions, text="忽略", variant="ghost", size="sm", command=lambda p=pid: self._ignore_pending_from_dialog(p, dialog), @@ -683,6 +688,21 @@ class PendingLearningUIMixin: # 保持 _pending_dialog_open / _pending_resume_live,重建列表 self.show_pending_dialog() + def _pl_place_center(self, win, width, height): + """把弹窗放到屏幕中央,避免默认贴在左上角。""" + try: + win.update_idletasks() + sw = win.winfo_screenwidth() + sh = win.winfo_screenheight() + width = min(int(width), max(sw - 48, 240)) + height = min(int(height), max(sh - 80, 180)) + x = max((sw - width) // 2, 0) + y = max((sh - height) // 2 - 24, 12) + win.geometry(f"{width}x{height}+{x}+{y}") + win.lift() + except Exception: + pass + def _preview_pending_image(self, image_path, parent=None): """待学习库:点击缩略图查看对应大图。""" if not image_path or not os.path.exists(image_path): @@ -699,16 +719,35 @@ class PendingLearningUIMixin: win.title("待学习图片预览") win.configure(bg=COLORS["bg"]) win.transient(parent or self.root) - im.thumbnail((760, 640)) + try: + sw = win.winfo_screenwidth() + sh = win.winfo_screenheight() + except Exception: + sw, sh = 1280, 800 + max_w = max(720, int(sw * 0.72)) + max_h = max(520, int(sh * 0.72)) + iw, ih = im.size + scale = min(max_w / float(iw or 1), max_h / float(ih or 1), 4.0) + if abs(scale - 1.0) > 0.02: + im = im.resize( + (max(1, int(iw * scale)), max(1, int(ih * scale))), + Image.Resampling.LANCZOS if hasattr(Image, "Resampling") else Image.LANCZOS, + ) photo = ImageTk.PhotoImage(im) win._photo = photo - tk.Label(win, image=photo, bg=COLORS["bg"]).pack(padx=12, pady=12) + tk.Label(win, image=photo, bg=COLORS["bg"]).pack(padx=16, pady=(16, 8)) tk.Label(win, text=os.path.basename(image_path), bg=COLORS["bg"], fg=COLORS["muted"], font=FONTS["small"]).pack() PillButton(win, text="关闭", variant="ghost", - command=win.destroy).pack(pady=(8, 12)) + command=win.destroy).pack(pady=(8, 14)) + win.update_idletasks() + self._pl_place_center( + win, + max(win.winfo_reqwidth(), im.width + 48), + max(win.winfo_reqheight(), im.height + 96), + ) - def _learn_pending_from_dialog(self, pending_id, suggested, dialog): + def _learn_pending_from_dialog(self, pending_id, suggested, dialog, row=None): """待学习库:下拉含主食小库 + 当前餐段菜品。""" period_name, period_names, values, initial, is_period = self._pl_learn_name_choices(suggested) @@ -716,7 +755,6 @@ class PendingLearningUIMixin: pick.title("选择菜品名称") pick.configure(bg=COLORS["bg"]) pick.transient(dialog) - pick.grab_set() pick.resizable(False, False) tk.Label(pick, text="学习到哪个菜名?", bg=COLORS["bg"], fg=COLORS["text"], @@ -728,24 +766,45 @@ class PendingLearningUIMixin: else: tip = "暂无候选菜品可选,请手动输入菜名。" tk.Label(pick, text=tip, bg=COLORS["bg"], fg=COLORS["muted"], font=FONTS["small"], - wraplength=340, justify=tk.LEFT).pack(padx=20, anchor=tk.W) + wraplength=420, justify=tk.LEFT).pack(padx=20, anchor=tk.W) if values: Chip(pick, text=f"主食+{period_name} · {len(values)} 道", tone="ok", bg=COLORS["bg"]).pack(padx=20, pady=(8, 0), anchor=tk.W) - name_var = tk.StringVar(value=initial) + name_var = tk.StringVar(value=initial or "") + row_pick = tk.Frame(pick, bg=COLORS["bg"]) + row_pick.pack(fill=tk.X, padx=20, pady=10) combo = ttk.Combobox( - pick, textvariable=name_var, values=values, - width=34, style="Large.TEntry", + row_pick, textvariable=name_var, values=values, width=28, ) - combo.pack(padx=20, pady=10, fill=tk.X) + combo["height"] = 16 + combo.pack(side=tk.LEFT, fill=tk.X, expand=True) + + def _open_dropdown(_event=None): + combo.focus_set() + try: + combo.tk.call("ttk::combobox::Post", combo._w) + except Exception: + try: + combo.event_generate("") + except Exception: + pass + return "break" + + PillButton(row_pick, text="选择", variant="secondary", size="sm", + command=_open_dropdown).pack(side=tk.LEFT, padx=(8, 0)) combo.focus_set() + if initial: + try: + combo.icursor("end") + except Exception: + pass if not values: tk.Label(pick, text="暂无可选菜名,请直接输入新菜名(将新建类别)。", bg=COLORS["bg"], fg=COLORS["warn"], font=FONTS["caption"], - wraplength=300, justify=tk.LEFT).pack(padx=20, anchor=tk.W) + wraplength=400, justify=tk.LEFT).pack(padx=20, anchor=tk.W) btns = tk.Frame(pick, bg=COLORS["bg"]) btns.pack(fill=tk.X, padx=20, pady=(10, 18)) @@ -761,16 +820,38 @@ class PendingLearningUIMixin: self.refresh_template_list() self.update_pending_btn() self._pl_toast(f"已学习为「{category}」并加入模板库", "ok") - dialog.destroy() - self.show_pending_dialog() + self._drop_pending_row(dialog, pending_id, row) else: messagebox.showerror("失败", "学习失败,请重试", parent=pick) PillButton(btns, text="确定学习", variant="primary", command=_confirm).pack(side=tk.RIGHT, padx=4) PillButton(btns, text="取消", variant="ghost", command=pick.destroy).pack(side=tk.RIGHT) pick.bind("", lambda e: _confirm()) + pick.update_idletasks() + self._pl_place_center(pick, max(pick.winfo_reqwidth(), 480), pick.winfo_reqheight()) pick.wait_window() + def _drop_pending_row(self, dialog, pending_id, row): + """学完只去掉当前行,列表停在原位置,不整页重建。""" + items = getattr(dialog, "_pending_items", None) + if isinstance(items, list): + dialog._pending_items = [ + it for it in items if it.get("id") != pending_id + ] + lbl = getattr(dialog, "_pending_count_label", None) + if lbl is not None: + try: + n = len(getattr(dialog, "_pending_items", []) or []) + lbl.config(text=f"共 {n} 条记录") + except Exception: + pass + if row is not None: + try: + if row.winfo_exists(): + row.destroy() + except Exception: + pass + def _ignore_pending_from_dialog(self, pending_id, dialog): if self.manager.ignore_pending(pending_id): self.update_pending_btn()