设置页面ui优化
This commit is contained in:
parent
8ab7004bcb
commit
2aee17b655
|
|
@ -1922,6 +1922,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
|||
stats.config(text=f"筛选 0 / {visible_n} 个菜品")
|
||||
else:
|
||||
stats.config(text="共 0 个菜品 · 0 张模板")
|
||||
self._scroll_template_list_top()
|
||||
return
|
||||
|
||||
dish_n = len(matched)
|
||||
|
|
@ -2026,6 +2027,22 @@ class BaseDishApp(PendingLearningUIMixin):
|
|||
text=f"共 {dish_n} 个菜品 · {total_tpl} 张模板 · 本页 {len(page_items)} 条"
|
||||
)
|
||||
|
||||
# 翻页 / 刷新后滚回列表顶部
|
||||
self._scroll_template_list_top()
|
||||
|
||||
def _scroll_template_list_top(self):
|
||||
canvas = getattr(self, "_template_canvas", None)
|
||||
if not self._widget_alive(canvas):
|
||||
return
|
||||
try:
|
||||
canvas.update_idletasks()
|
||||
canvas.yview_moveto(0)
|
||||
bbox = canvas.bbox("all")
|
||||
if bbox:
|
||||
canvas.configure(scrollregion=bbox)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 实时视频:流畅预览 + 约 1fps 识别(框叠加)
|
||||
# ---------------------------------------------------------------
|
||||
|
|
@ -2868,53 +2885,109 @@ class BaseDishApp(PendingLearningUIMixin):
|
|||
if not template_details:
|
||||
messagebox.showinfo("提示", "该类别暂无模板信息")
|
||||
return
|
||||
detail_win = self._dialog_shell(f"模板详情 - {part_type}", 1000, 780)
|
||||
detail_win = self._dialog_shell(f"模板详情 - {part_type}", 1040, 760)
|
||||
detail_win._detail_photos = [] # 防止 PhotoImage 被回收
|
||||
|
||||
tk.Label(detail_win, text=f"{part_type} · 共 {len(template_details)} 张模板",
|
||||
bg=COLORS["bg"], fg=COLORS["text"], font=FONTS["h1"],
|
||||
).pack(pady=(14, 8), padx=16, anchor=tk.W)
|
||||
head = tk.Frame(detail_win, bg=COLORS["bg"])
|
||||
head.pack(fill=tk.X, padx=16, pady=(14, 6))
|
||||
tk.Label(
|
||||
head, text=f"{part_type}",
|
||||
bg=COLORS["bg"], fg=COLORS["text"], font=FONTS["h1"],
|
||||
).pack(side=tk.LEFT)
|
||||
tk.Label(
|
||||
head, text=f"共 {len(template_details)} 张 · 点击缩略图可放大",
|
||||
bg=COLORS["bg"], fg=COLORS["muted"], font=FONTS["small"],
|
||||
).pack(side=tk.LEFT, padx=(10, 0), pady=(6, 0))
|
||||
|
||||
wrap, inner, _cv = self._make_scroll_area(detail_win, COLORS["bg"])
|
||||
wrap.pack(fill=tk.BOTH, expand=True, padx=14, pady=(0, 8))
|
||||
|
||||
for idx, temp in enumerate(template_details, 1):
|
||||
card = RoundCard(inner, title=f"模板 {idx}", radius=RADIUS["lg"], padding=10)
|
||||
card.pack(fill=tk.X, pady=6)
|
||||
if card.header is not None:
|
||||
PillButton(
|
||||
card.header, text="删除此模板", variant="danger", size="sm",
|
||||
command=lambda p=part_type, i=idx - 1: self.delete_single_template(p, i),
|
||||
).pack(side=tk.RIGHT)
|
||||
tk.Label(card.body, text=f"添加时间:{temp.get('add_time') or '—'}",
|
||||
bg=COLORS["surface"], fg=COLORS["text_body"], font=FONTS["ui"],
|
||||
anchor="w").pack(fill=tk.X)
|
||||
tk.Label(card.body, text=f"文件路径:{temp.get('path') or ''}",
|
||||
bg=COLORS["surface"], fg=COLORS["muted"], font=FONTS["small"],
|
||||
anchor="w", wraplength=860, justify=tk.LEFT).pack(fill=tk.X, pady=(2, 6))
|
||||
# 统一网格缩略图,避免首张超大、后面挤在一起
|
||||
cols = 4
|
||||
cell_w, cell_h = 220, 200
|
||||
grid = tk.Frame(inner, bg=COLORS["bg"])
|
||||
grid.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
|
||||
for col in range(cols):
|
||||
grid.columnconfigure(col, weight=1, minsize=cell_w + 16)
|
||||
|
||||
# 首张大图,其余只显示小图,避免一次解码过多大图
|
||||
def _preview_large(path, title):
|
||||
if not path or not os.path.isfile(str(path)):
|
||||
messagebox.showwarning("提示", "图片不存在", parent=detail_win)
|
||||
return
|
||||
prev = self._dialog_shell(title or "模板预览", 920, 700)
|
||||
prev.transient(detail_win)
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
img = Image.open(f).convert("RGB")
|
||||
img.thumbnail((880, 620), Image.Resampling.LANCZOS)
|
||||
photo = ImageTk.PhotoImage(img)
|
||||
prev._photo = photo
|
||||
tk.Label(prev, image=photo, bg=COLORS["bg"]).pack(padx=12, pady=12)
|
||||
tk.Label(
|
||||
prev, text=os.path.basename(str(path)),
|
||||
bg=COLORS["bg"], fg=COLORS["muted"], font=FONTS["small"],
|
||||
).pack()
|
||||
except Exception as exc:
|
||||
tk.Label(
|
||||
prev, text=f"加载失败:{exc}",
|
||||
bg=COLORS["bg"], fg=COLORS["danger"], font=FONTS["ui"],
|
||||
).pack(pady=40)
|
||||
PillButton(prev, text="关闭", variant="ghost", command=prev.destroy).pack(pady=(8, 14))
|
||||
|
||||
for idx, temp in enumerate(template_details):
|
||||
r, c = divmod(idx, cols)
|
||||
cell = tk.Frame(
|
||||
grid, bg=COLORS["surface"],
|
||||
highlightbackground=COLORS["border"], highlightthickness=1,
|
||||
)
|
||||
cell.grid(row=r, column=c, padx=6, pady=6, sticky="nsew")
|
||||
pad = tk.Frame(cell, bg=COLORS["surface"])
|
||||
pad.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
|
||||
|
||||
img_path = temp.get("path") or temp.get("thumbnail")
|
||||
thumb_holder = tk.Frame(pad, bg=COLORS["surface_alt"], width=cell_w, height=140)
|
||||
thumb_holder.pack(fill=tk.X)
|
||||
thumb_holder.pack_propagate(False)
|
||||
try:
|
||||
img_path = temp.get("path")
|
||||
if not img_path or not os.path.isfile(str(img_path)):
|
||||
raise FileNotFoundError("图片不存在")
|
||||
with open(img_path, "rb") as f:
|
||||
img = Image.open(f).convert("RGB")
|
||||
if idx == 1:
|
||||
img.thumbnail((860, 520), Image.Resampling.LANCZOS)
|
||||
else:
|
||||
img.thumbnail((220, 160), Image.Resampling.LANCZOS)
|
||||
img.thumbnail((cell_w, 140), Image.Resampling.LANCZOS)
|
||||
photo = ImageTk.PhotoImage(img)
|
||||
detail_win._detail_photos.append(photo)
|
||||
lbl = tk.Label(card.body, image=photo, bg=COLORS["surface"], bd=0)
|
||||
lbl = tk.Label(thumb_holder, image=photo, bg=COLORS["surface_alt"], bd=0, cursor="hand2")
|
||||
lbl.image = photo
|
||||
lbl.pack()
|
||||
lbl.pack(expand=True)
|
||||
lbl.bind(
|
||||
"<Button-1>",
|
||||
lambda e, p=img_path, t=f"{part_type} · 模板 {idx + 1}": _preview_large(p, t),
|
||||
)
|
||||
except Exception as e:
|
||||
tk.Label(card.body, text=f"图片加载失败:{e}", bg=COLORS["surface"],
|
||||
fg=COLORS["danger"], font=FONTS["small"]).pack(pady=4)
|
||||
tk.Label(
|
||||
thumb_holder, text=f"加载失败\n{e}",
|
||||
bg=COLORS["surface_alt"], fg=COLORS["danger"],
|
||||
font=FONTS["caption"], justify=tk.CENTER,
|
||||
).pack(expand=True)
|
||||
|
||||
PillButton(detail_win, text="关闭", variant="ghost",
|
||||
command=detail_win.destroy).pack(pady=(0, 12))
|
||||
meta = tk.Frame(pad, bg=COLORS["surface"])
|
||||
meta.pack(fill=tk.X, pady=(8, 0))
|
||||
tk.Label(
|
||||
meta, text=f"#{idx + 1}", bg=COLORS["surface"], fg=COLORS["text"],
|
||||
font=FONTS["ui_bold"],
|
||||
).pack(side=tk.LEFT)
|
||||
tk.Label(
|
||||
meta, text=temp.get("add_time") or "—",
|
||||
bg=COLORS["surface"], fg=COLORS["muted"], font=FONTS["caption"],
|
||||
).pack(side=tk.LEFT, padx=(8, 0))
|
||||
PillButton(
|
||||
meta, text="删除", variant="danger", size="sm",
|
||||
command=lambda p=part_type, i=idx: self.delete_single_template(p, i),
|
||||
).pack(side=tk.RIGHT)
|
||||
|
||||
foot = tk.Frame(detail_win, bg=COLORS["bg"])
|
||||
foot.pack(fill=tk.X, padx=16, pady=(0, 12))
|
||||
PillButton(foot, text="关闭", variant="ghost", command=detail_win.destroy).pack(side=tk.RIGHT)
|
||||
|
||||
def center_window(self, window, width, height):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -408,19 +408,22 @@ class PillButton(tk.Frame):
|
|||
if self._disabled:
|
||||
return c["disabled_bg"], "#FFFFFF", c["disabled_bg"], False
|
||||
if self._variant == "primary":
|
||||
# 按下比悬停更深,反馈更明显
|
||||
if self._pressed:
|
||||
return c["primary_dark"], "#FFFFFF", c["primary_dark"], True
|
||||
return "#1A6B45", "#FFFFFF", "#1A6B45", False
|
||||
if self._hover:
|
||||
return c["primary_dark"], "#FFFFFF", c["primary_dark"], True
|
||||
return c["primary"], "#FFFFFF", c["primary"], True
|
||||
if self._variant == "secondary":
|
||||
if self._pressed:
|
||||
return c["primary_border"], c["primary_dark"], c["primary"], True
|
||||
return c["primary"], "#FFFFFF", c["primary"], False
|
||||
if self._hover:
|
||||
return c["primary_soft"], c["primary_dark"], c["primary"], True
|
||||
return c["surface"], c["primary"], c["primary_border"], True
|
||||
if self._variant == "danger":
|
||||
if self._hover or self._pressed:
|
||||
if self._pressed:
|
||||
return "#9B1C1C", "#FFFFFF", "#9B1C1C", False
|
||||
if self._hover:
|
||||
return c["danger_dark"], "#FFFFFF", c["danger_dark"], False
|
||||
return c["surface"], c["danger"], c["danger_border"], False
|
||||
# ghost
|
||||
|
|
@ -447,8 +450,15 @@ class PillButton(tk.Frame):
|
|||
self._canvas.configure(width=w + 2, height=h + 2, bg=self["bg"])
|
||||
if shadow and not self._disabled and not self._pressed:
|
||||
_round_rect(self._canvas, 2, 2, w + 1, h + 1, r=RADIUS["md"], fill=COLORS["shadow"], outline="")
|
||||
offset = 1 if (self._pressed and not self._disabled) else 0
|
||||
_round_rect(self._canvas, 0, offset, w - 1, h - 1 + offset, r=RADIUS["md"], fill=fill, outline=border)
|
||||
# 按下时整体下沉 + 略缩小,点击感更清晰
|
||||
offset = 2 if (self._pressed and not self._disabled) else 0
|
||||
inset = 1 if (self._pressed and not self._disabled) else 0
|
||||
_round_rect(
|
||||
self._canvas,
|
||||
inset, offset + inset,
|
||||
w - 1 - inset, h - 1 + offset - inset,
|
||||
r=RADIUS["md"], fill=fill, outline=border,
|
||||
)
|
||||
self._canvas.create_text(w // 2, h // 2 + offset, text=label, fill=fg, font=font)
|
||||
|
||||
def _on_enter(self, _e=None):
|
||||
|
|
@ -467,8 +477,9 @@ class PillButton(tk.Frame):
|
|||
return
|
||||
self._pressed = True
|
||||
self._draw()
|
||||
self._canvas.update_idletasks()
|
||||
|
||||
def _on_release(self, _e=None):
|
||||
def _on_release(self, event=None):
|
||||
if self._disabled or not self._command:
|
||||
self._pressed = False
|
||||
self._draw()
|
||||
|
|
@ -476,8 +487,29 @@ class PillButton(tk.Frame):
|
|||
was = self._pressed
|
||||
self._pressed = False
|
||||
self._draw()
|
||||
if was:
|
||||
self._command()
|
||||
if not was:
|
||||
return
|
||||
# 松手仍在按钮内才触发,避免拖出后误点
|
||||
try:
|
||||
x = getattr(event, "x", 0)
|
||||
y = getattr(event, "y", 0)
|
||||
w = max(self._canvas.winfo_width(), 1)
|
||||
h = max(self._canvas.winfo_height(), 1)
|
||||
if not (0 <= x <= w and 0 <= y <= h):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
cmd = self._command
|
||||
# 延后一拍执行,先完成按下回弹绘制;弹窗打开时也不卡死视觉态
|
||||
self.after(1, lambda: self._safe_fire(cmd))
|
||||
|
||||
def _safe_fire(self, cmd):
|
||||
if self._disabled or not cmd:
|
||||
return
|
||||
try:
|
||||
cmd()
|
||||
except Exception as exc:
|
||||
print(f"[PillButton] command failed: {exc}")
|
||||
|
||||
|
||||
class IconButton(tk.Label):
|
||||
|
|
|
|||
Loading…
Reference in New Issue