设置页面ui优化

This commit is contained in:
LiXin01 2026-10-09 16:38:05 +08:00
parent 8ab7004bcb
commit 2aee17b655
2 changed files with 143 additions and 38 deletions

View File

@ -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:

View File

@ -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):