产品/客户搜索改为输入框下方下拉列表,选中后阻止trace重复触发搜索

This commit is contained in:
2026-05-15 16:25:34 +08:00
parent ed62b49921
commit 6caa6deaa3
+31 -27
View File
@@ -217,9 +217,11 @@ class App:
r0 = Frame(fixed_frame) r0 = Frame(fixed_frame)
r0.pack(fill=X, pady=2) r0.pack(fill=X, pady=2)
Label(r0, text="产品:", width=10, anchor="e").pack(side="left") Label(r0, text="产品:", width=10, anchor="e").pack(side="left")
Entry(r0, textvariable=self.fv_product, width=20).pack(side="left", padx=5) self.fv_product_entry = Entry(r0, textvariable=self.fv_product, width=20)
self.fv_product_entry.pack(side="left", padx=5)
Label(r0, text="客户:", width=10, anchor="e").pack(side="left", padx=(30, 0)) Label(r0, text="客户:", width=10, anchor="e").pack(side="left", padx=(30, 0))
Entry(r0, textvariable=self.fv_customer, width=20).pack(side="left", padx=5) self.fv_customer_entry = Entry(r0, textvariable=self.fv_customer, width=20)
self.fv_customer_entry.pack(side="left", padx=5)
# Row 1: 寄达地 | 无影响 # Row 1: 寄达地 | 无影响
r1 = Frame(fixed_frame) r1 = Frame(fixed_frame)
@@ -443,7 +445,7 @@ class App:
return return
names = [c.get("productName", "") for c in candidates] names = [c.get("productName", "") for c in candidates]
self._show_search_dropdown( self._show_search_dropdown(
"选择产品", names, self.fv_product_entry, names,
lambda idx: self._on_product_selected(candidates[idx]), lambda idx: self._on_product_selected(candidates[idx]),
) )
except Exception as e: except Exception as e:
@@ -461,26 +463,28 @@ class App:
for c in candidates for c in candidates
] ]
self._show_search_dropdown( self._show_search_dropdown(
"选择客户", names, self.fv_customer_entry, names,
lambda idx: self._on_customer_selected(candidates[idx]), lambda idx: self._on_customer_selected(candidates[idx]),
) )
except Exception as e: except Exception as e:
self._log(f"[error] 客户搜索失败: {e}") self._log(f"[error] 客户搜索失败: {e}")
def _show_search_dropdown(self, title: str, options: list[str], on_select): def _show_search_dropdown(self, entry: Entry, options: list[str], on_select):
"""在屏幕中央弹出下拉选择窗口。""" """在输入框正下方弹出下拉候选列表。"""
popup = Toplevel(self.root) popup = Toplevel(self.root)
popup.title(title) popup.overrideredirect(True)
popup.geometry("360x260")
popup.transient(self.root)
popup.grab_set()
popup.attributes("-topmost", True) popup.attributes("-topmost", True)
Label(popup, text=title, font=("", 10, "bold"), pady=6).pack() # 定位在输入框正下方
x = entry.winfo_rootx()
y = entry.winfo_rooty() + entry.winfo_height()
w = entry.winfo_width()
popup.geometry(f"{w}x180+{x}+{y}")
frame = Frame(popup) frame = Frame(popup, bd=1, relief="solid")
frame.pack(fill="both", expand=True, padx=10, pady=5) frame.pack(fill="both", expand=True)
listbox = Listbox(frame, font=("", 10))
listbox = Listbox(frame, font=("", 10), selectmode="browse", activestyle="none")
scroll = Scrollbar(frame, orient=VERTICAL, command=listbox.yview) scroll = Scrollbar(frame, orient=VERTICAL, command=listbox.yview)
listbox.configure(yscrollcommand=scroll.set) listbox.configure(yscrollcommand=scroll.set)
scroll.pack(side=RIGHT, fill=Y) scroll.pack(side=RIGHT, fill=Y)
@@ -488,37 +492,35 @@ class App:
for opt in options: for opt in options:
listbox.insert(END, opt) listbox.insert(END, opt)
chosen = [False] def select(e=None):
def select():
sel = listbox.curselection() sel = listbox.curselection()
if sel: if sel:
chosen[0] = True
popup.destroy() popup.destroy()
on_select(sel[0]) on_select(sel[0])
def cancel(): def cancel(e=None):
popup.destroy() popup.destroy()
listbox.bind("<Double-1>", lambda e: select()) listbox.bind("<ButtonRelease-1>", select)
listbox.bind("<Return>", lambda e: select()) listbox.bind("<Return>", select)
listbox.bind("<Escape>", lambda e: cancel()) listbox.bind("<Escape>", cancel)
listbox.focus_set() listbox.focus_set()
if listbox.size() > 0: if listbox.size() > 0:
listbox.selection_set(0) listbox.selection_set(0)
btn_f = Frame(popup) # 点击外部关闭
btn_f.pack(pady=8) def on_focus_out(e):
Button(btn_f, text="选择", command=select, bg="#4CAF50", fg="white", width=8).pack(side=LEFT, padx=5) popup.after(150, lambda: popup.destroy() if not popup.focus_get() else None)
Button(btn_f, text="取消", command=cancel, width=8).pack(side=LEFT, padx=5) popup.bind("<FocusOut>", on_focus_out)
popup.protocol("WM_DELETE_WINDOW", cancel)
popup.wait_window() popup.wait_window()
def _on_product_selected(self, choice: dict): def _on_product_selected(self, choice: dict):
"""产品选中:缓存选择并获取详情。""" """产品选中:缓存选择并获取详情。"""
self._product_cached_choice = choice self._product_cached_choice = choice
self._loading_config = True # 阻止 trace 再次触发搜索
self.fv_product.set(choice.get("productName", "")) self.fv_product.set(choice.get("productName", ""))
self._loading_config = False
# 获取产品详情 # 获取产品详情
try: try:
biz_id = choice.get("productId", "") biz_id = choice.get("productId", "")
@@ -532,7 +534,9 @@ class App:
def _on_customer_selected(self, choice: dict): def _on_customer_selected(self, choice: dict):
"""客户选中:缓存选择并获取详情和寄件人实名。""" """客户选中:缓存选择并获取详情和寄件人实名。"""
self._customer_cached_choice = choice self._customer_cached_choice = choice
self._loading_config = True # 阻止 trace 再次触发搜索
self.fv_customer.set(choice.get("customerFullName", choice.get("customerName", ""))) self.fv_customer.set(choice.get("customerFullName", choice.get("customerName", "")))
self._loading_config = False
# 获取客户详情 # 获取客户详情
try: try:
cust_id = choice.get("customerId", "") cust_id = choice.get("customerId", "")