"""商品记忆库查看/编辑对话框""" import os import tkinter as tk from tkinter import ttk, messagebox, simpledialog from app.config.settings import ConfigManager from app.core.db.product_db import ProductDatabase from .ui_widgets import center_window def _get_product_db(): cfg = ConfigManager() db_path = cfg.get_path('Paths', 'product_db', fallback='data/product_cache.db') if hasattr(cfg, 'get_path') else 'data/product_cache.db' tpl_folder = cfg.get('Paths', 'template_folder', fallback='templates') item_data = cfg.get('Templates', 'item_data', fallback='商品资料.xlsx') tpl_path = os.path.join(tpl_folder, item_data) return ProductDatabase(db_path, tpl_path) def show_memory_editor(root): """显示商品记忆库编辑器""" db = _get_product_db() dlg = tk.Toplevel(root) dlg.title("商品记忆库") dlg.geometry("950x520") center_window(dlg) # ── 顶部搜索栏 ── top = ttk.Frame(dlg) top.pack(fill=tk.X, padx=8, pady=(8, 4)) ttk.Label(top, text="搜索:").pack(side=tk.LEFT) search_var = tk.StringVar() search_entry = ttk.Entry(top, textvariable=search_var, width=30) search_entry.pack(side=tk.LEFT, padx=4) # ── 统计标签 ── stats_label = ttk.Label(top, text="") stats_label.pack(side=tk.RIGHT) # ── Treeview ── columns = ("barcode", "name", "specification", "unit", "price", "source", "confidence", "usage_count", "last_seen") tree = ttk.Treeview(dlg, columns=columns, show="headings", height=18) headers = { "barcode": ("条码", 120), "name": ("名称", 180), "specification": ("规格", 80), "unit": ("单位", 50), "price": ("单价", 70), "source": ("来源", 80), "confidence": ("置信度", 60), "usage_count": ("使用次数", 70), "last_seen": ("最后使用", 140), } for col, (text, width) in headers.items(): tree.heading(col, text=text) tree.column(col, width=width, anchor="center") # 置信度颜色标签 tree.tag_configure("high", foreground="#28a745") # >= 80 绿 tree.tag_configure("medium", foreground="#ffc107") # 50-79 黄 tree.tag_configure("low", foreground="#dc3545") # < 50 红 scrollbar = ttk.Scrollbar(dlg, orient=tk.VERTICAL, command=tree.yview) tree.configure(yscrollcommand=scrollbar.set) tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0), pady=4) scrollbar.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 8), pady=4) # ── 数据加载 ── all_records = [] def load_data(filter_text=""): nonlocal all_records all_records = db.get_all_memories() tree.delete(*tree.get_children()) filtered = all_records if filter_text: ft = filter_text.lower() filtered = [r for r in all_records if ft in str(r.get('barcode', '')).lower() or ft in str(r.get('name', '')).lower()] for r in filtered: conf = r.get('confidence', 0) or 0 tag = "high" if conf >= 80 else ("medium" if conf >= 50 else "low") last_seen = r.get('last_seen', '') or '' if last_seen and len(last_seen) > 16: last_seen = last_seen[:16] source_display = { 'template': '模板', 'ocr': 'OCR', 'user_confirmed': '手动', }.get(r.get('source', ''), r.get('source', '')) tree.insert("", tk.END, values=( r.get('barcode', ''), r.get('name', ''), r.get('specification', ''), r.get('unit', ''), f"{r.get('price', 0):.2f}" if r.get('price') else '', source_display, conf, r.get('usage_count', 0) or 0, last_seen, ), tags=(tag,)) stats_label.config(text=f"共 {len(filtered)} / {len(all_records)} 条") def on_search(*_): load_data(search_var.get()) search_var.trace_add("write", on_search) # ── 按钮区 ── btn_frame = ttk.Frame(dlg) btn_frame.pack(fill=tk.X, padx=8, pady=(0, 8)) def edit_selected(): sel = tree.selection() if not sel: messagebox.showwarning("提示", "请先选择一条记录") return item = tree.item(sel[0]) vals = item['values'] barcode = vals[0] # 弹出编辑对话框 edit_dlg = tk.Toplevel(dlg) edit_dlg.title(f"编辑: {barcode}") edit_dlg.geometry("380x260") center_window(edit_dlg) fields = [ ("名称", "name", vals[1]), ("规格", "specification", vals[2]), ("单位", "unit", vals[3]), ("单价", "price", vals[4]), ] entries = {} for i, (label, key, val) in enumerate(fields): ttk.Label(edit_dlg, text=label).grid(row=i, column=0, sticky='w', padx=8, pady=4) var = tk.StringVar(value=str(val) if val else '') ttk.Entry(edit_dlg, textvariable=var, width=30).grid(row=i, column=1, padx=8, pady=4) entries[key] = var def save_edit(): updates = {} for key, var in entries.items(): v = var.get().strip() if key == 'price': try: updates[key] = float(v) if v else 0 except ValueError: updates[key] = 0 else: updates[key] = v db.update_memory(barcode, updates) edit_dlg.destroy() load_data(search_var.get()) ttk.Button(edit_dlg, text="保存", command=save_edit).grid(row=len(fields), column=0, columnspan=2, pady=12) def delete_selected(): sel = tree.selection() if not sel: messagebox.showwarning("提示", "请先选择一条记录") return item = tree.item(sel[0]) barcode = item['values'][0] if messagebox.askyesno("确认删除", f"确定要删除条码 {barcode} 的记忆记录吗?"): db.delete_memory(barcode) load_data(search_var.get()) def reimport_template(): if messagebox.askyesno("确认", "重新从商品资料导入将重置所有模板商品的置信度为100,确定继续吗?"): count = db.reimport() messagebox.showinfo("完成", f"已重新导入 {count} 条记录") load_data(search_var.get()) ttk.Button(btn_frame, text="编辑", command=edit_selected).pack(side=tk.LEFT, padx=4) ttk.Button(btn_frame, text="删除", command=delete_selected).pack(side=tk.LEFT, padx=4) ttk.Button(btn_frame, text="重新导入模板", command=reimport_template).pack(side=tk.LEFT, padx=4) ttk.Button(btn_frame, text="刷新", command=lambda: load_data(search_var.get())).pack(side=tk.LEFT, padx=4) ttk.Button(btn_frame, text="关闭", command=dlg.destroy).pack(side=tk.RIGHT, padx=4) # 双击编辑 tree.bind("", lambda e: edit_selected()) # 初始加载 load_data()