import tkinter as tk from tkinter import ttk, messagebox, simpledialog from datetime import datetime import json from inventory_manager import InventoryManager class InventoryUI: def __init__(self): self.root = tk.Tk() self.root.title("出入库管理系统") self.root.geometry("800x600") self.root.resizable(False, False) # 初始化库存管理器 self.manager = InventoryManager("inventory_data.json") # 当前页面状态 self.current_page = "main" self.current_product = None # 创建主界面 self.create_main_page() def create_main_page(self): """创建主页面 - 产品列表""" self.clear_window() # 顶部搜索栏 search_frame = tk.Frame(self.root) search_frame.pack(fill=tk.X, padx=10, pady=5) tk.Label(search_frame, text="搜索产品:").pack(side=tk.LEFT) self.search_entry = tk.Entry(search_frame, width=30) self.search_entry.pack(side=tk.LEFT, padx=5) tk.Button(search_frame, text="搜索", command=self.search_products).pack(side=tk.LEFT, padx=2) tk.Button(search_frame, text="清空", command=self.clear_search).pack(side=tk.LEFT, padx=2) tk.Button(search_frame, text="添加产品分类", command=self.show_add_product_page).pack(side=tk.LEFT, padx=2) tk.Button(search_frame, text="查询", command=self.show_query_page).pack(side=tk.LEFT, padx=2) # 产品列表表格 columns = ("产品名", "当前剩余重量(kg)", "平均价格(元/kg)", "操作") self.product_tree = ttk.Treeview(self.root, columns=columns, show="headings", height=15) for col in columns: self.product_tree.heading(col, text=col) self.product_tree.column(col, width=180, anchor=tk.CENTER) self.product_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 绑定双击事件 self.product_tree.bind("", self.on_product_select) # 底部统计信息 stats_frame = tk.Frame(self.root) stats_frame.pack(fill=tk.X, padx=10, pady=5) self.stats_label = tk.Label(stats_frame, text="库存总价值: 0.00 元 库存总重: 0.0 kg") self.stats_label.pack(side=tk.RIGHT) # 分页控件 page_frame = tk.Frame(self.root) page_frame.pack(fill=tk.X, padx=10, pady=5) tk.Button(page_frame, text="上一页").pack(side=tk.LEFT) tk.Label(page_frame, text="第 1 页 / 共 1 页").pack(side=tk.LEFT, padx=10) tk.Button(page_frame, text="下一页").pack(side=tk.LEFT) # 加载产品数据 self.load_products() def create_product_detail_page(self, product_name): """创建产品详情页面""" self.clear_window() self.current_product = product_name # 顶部标题和按钮 header_frame = tk.Frame(self.root) header_frame.pack(fill=tk.X, padx=10, pady=5) tk.Label(header_frame, text=f"产品: {product_name}", font=("Arial", 14, "bold")).pack(side=tk.LEFT) button_frame = tk.Frame(header_frame) button_frame.pack(side=tk.RIGHT) tk.Button(button_frame, text="入库", bg="lightgreen", command=self.show_inbound_dialog).pack(side=tk.LEFT, padx=2) tk.Button(button_frame, text="出库", bg="lightcoral", command=self.show_outbound_dialog).pack(side=tk.LEFT, padx=2) tk.Button(button_frame, text="删除产品", bg="red", fg="white", command=self.delete_product).pack(side=tk.LEFT, padx=2) tk.Button(button_frame, text="返回主页", command=self.create_main_page).pack(side=tk.LEFT, padx=2) # 交易记录表格 columns = ("时间", "类型", "重量(kg)", "价格(元/kg)", "总价(元)", "备注", "操作") self.transaction_tree = ttk.Treeview(self.root, columns=columns, show="headings", height=18) for col in columns: self.transaction_tree.heading(col, text=col) if col == "时间": self.transaction_tree.column(col, width=100) elif col == "类型": self.transaction_tree.column(col, width=80) elif col == "操作": self.transaction_tree.column(col, width=80) else: self.transaction_tree.column(col, width=100) self.transaction_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 绑定删除事件 self.transaction_tree.bind("", self.delete_transaction) # 加载交易记录 self.load_transactions(product_name) def create_add_product_page(self): """创建添加产品页面""" self.clear_window() # 标题 tk.Label(self.root, text="添加产品大类", font=("Arial", 16, "bold")).pack(pady=20) # 表单 form_frame = tk.Frame(self.root) form_frame.pack(pady=50) tk.Label(form_frame, text="产品名称:").grid(row=0, column=0, sticky=tk.W, padx=10, pady=10) self.product_name_entry = tk.Entry(form_frame, width=30) self.product_name_entry.grid(row=0, column=1, padx=10, pady=10) tk.Label(form_frame, text="产品描述:").grid(row=1, column=0, sticky=tk.W, padx=10, pady=10) self.product_desc_entry = tk.Entry(form_frame, width=30) self.product_desc_entry.grid(row=1, column=1, padx=10, pady=10) # 按钮 button_frame = tk.Frame(self.root) button_frame.pack(pady=30) tk.Button(button_frame, text="确认添加", bg="lightblue", command=self.add_product).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="取消", command=self.create_main_page).pack(side=tk.LEFT, padx=10) # 返回按钮 tk.Button(self.root, text="返回主页", command=self.create_main_page).pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=10) # 说明文字 info_frame = tk.Frame(self.root) info_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=20) tk.Label(info_frame, text="说明:", font=("Arial", 10, "bold")).pack(anchor=tk.W) tk.Label(info_frame, text="• 产品名称不能为空且不能与现有产品重复").pack(anchor=tk.W) tk.Label(info_frame, text="• 产品描述为可选项").pack(anchor=tk.W) tk.Label(info_frame, text="• 添加产品后,可通过入库操作添加库存").pack(anchor=tk.W) def create_query_page(self): """创建订单查询页面""" self.clear_window() # 标题 tk.Label(self.root, text="订单查询", font=("Arial", 16, "bold")).pack(pady=10) # 查询条件 query_frame = tk.Frame(self.root) query_frame.pack(fill=tk.X, padx=10, pady=10) # 第一行 row1 = tk.Frame(query_frame) row1.pack(fill=tk.X, pady=5) tk.Label(row1, text="产品名称:").pack(side=tk.LEFT) self.query_product_entry = tk.Entry(row1, width=15) self.query_product_entry.pack(side=tk.LEFT, padx=5) tk.Label(row1, text="交易类型:").pack(side=tk.LEFT, padx=(20,5)) self.query_type_var = tk.StringVar(value="全部") type_combo = ttk.Combobox(row1, textvariable=self.query_type_var, values=["全部", "入库", "出库"], width=10) type_combo.pack(side=tk.LEFT, padx=5) # 第二行 row2 = tk.Frame(query_frame) row2.pack(fill=tk.X, pady=5) tk.Label(row2, text="开始时间:").pack(side=tk.LEFT) self.start_date_entry = tk.Entry(row2, width=12) self.start_date_entry.pack(side=tk.LEFT, padx=5) self.start_date_entry.insert(0, "2025-06-26") tk.Label(row2, text="(格式:2025-7-1)").pack(side=tk.LEFT) tk.Label(row2, text="结束时间:").pack(side=tk.LEFT, padx=(20,5)) self.end_date_entry = tk.Entry(row2, width=12) self.end_date_entry.pack(side=tk.LEFT, padx=5) self.end_date_entry.insert(0, "2025-07-26") tk.Label(row2, text="(格式:2025-12-31)").pack(side=tk.LEFT) # 第三行 row3 = tk.Frame(query_frame) row3.pack(fill=tk.X, pady=5) tk.Label(row3, text="最小重量:").pack(side=tk.LEFT) self.min_weight_entry = tk.Entry(row3, width=12) self.min_weight_entry.pack(side=tk.LEFT, padx=5) tk.Label(row3, text="最大重量:").pack(side=tk.LEFT, padx=(20,5)) self.max_weight_entry = tk.Entry(row3, width=12) self.max_weight_entry.pack(side=tk.LEFT, padx=5) tk.Label(row3, text="备注关键词:").pack(side=tk.LEFT, padx=(20,5)) self.note_keyword_entry = tk.Entry(row3, width=15) self.note_keyword_entry.pack(side=tk.LEFT, padx=5) # 查询按钮 button_frame = tk.Frame(query_frame) button_frame.pack(fill=tk.X, pady=10) tk.Button(button_frame, text="查询", bg="lightgreen", command=self.execute_query).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="清空条件", command=self.clear_query_conditions).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="打印查询结果", bg="lightblue", command=self.print_query_results).pack(side=tk.LEFT, padx=5) # 快速查询按钮 quick_frame = tk.Frame(button_frame) quick_frame.pack(side=tk.RIGHT) tk.Label(quick_frame, text="快速查询:").pack(side=tk.LEFT, padx=(20,5)) tk.Button(quick_frame, text="今日", command=lambda: self.quick_query("today"), bg="lightyellow").pack(side=tk.LEFT, padx=2) tk.Button(quick_frame, text="本周", command=lambda: self.quick_query("week"), bg="lightyellow").pack(side=tk.LEFT, padx=2) tk.Button(quick_frame, text="本月", command=lambda: self.quick_query("month"), bg="lightyellow").pack(side=tk.LEFT, padx=2) tk.Button(quick_frame, text="全部", command=lambda: self.quick_query("all"), bg="lightyellow").pack(side=tk.LEFT, padx=2) # 查询结果表格 columns = ("时间", "产品名", "类型", "重量(kg)", "价格(元/kg)", "总价(元)", "备注") self.query_tree = ttk.Treeview(self.root, columns=columns, show="headings", height=12) for col in columns: self.query_tree.heading(col, text=col) self.query_tree.column(col, width=100, anchor=tk.CENTER) self.query_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 底部统计和分页 bottom_frame = tk.Frame(self.root) bottom_frame.pack(fill=tk.X, padx=10, pady=5) # 分页 page_frame = tk.Frame(bottom_frame) page_frame.pack(side=tk.LEFT) # 初始化分页变量 self.current_page = 1 self.page_size = 20 # 每页显示20条记录 self.total_records = 0 self.filtered_transactions = [] # 存储过滤后的交易记录 self.prev_button = tk.Button(page_frame, text="上一页", command=self.prev_page) self.prev_button.pack(side=tk.LEFT) self.page_info_label = tk.Label(page_frame, text="第 1 页 / 共 1 页 (共 0 条记录)") self.page_info_label.pack(side=tk.LEFT, padx=10) self.next_button = tk.Button(page_frame, text="下一页", command=self.next_page) self.next_button.pack(side=tk.LEFT) # 每页显示数量选择 tk.Label(page_frame, text="每页显示:").pack(side=tk.LEFT, padx=(20,5)) self.page_size_var = tk.StringVar(value="20") page_size_combo = ttk.Combobox(page_frame, textvariable=self.page_size_var, values=["10", "20", "50", "100"], width=8) page_size_combo.pack(side=tk.LEFT, padx=5) page_size_combo.bind("<>", self.on_page_size_change) # 统计信息 self.query_stats_label = tk.Label(bottom_frame, text="总重量: 360.0kg 总价: 11680.00元") self.query_stats_label.pack(side=tk.RIGHT) # 返回按钮 tk.Button(self.root, text="返回主页", command=self.create_main_page).pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=5) def clear_window(self): """清空窗口内容""" for widget in self.root.winfo_children(): widget.destroy() def load_products(self): """加载产品列表""" try: with open("inventory_data.json", "r", encoding="utf-8") as f: data = json.load(f) # 清空现有数据 for item in self.product_tree.get_children(): self.product_tree.delete(item) total_value = 0 total_weight = 0 for product_name, product_data in data["products"].items(): weight = float(product_data["total_weight"]) price = float(product_data["avg_price"]) value = weight * price total_weight += weight total_value += value self.product_tree.insert("", tk.END, values=( product_name, f"{weight:.8f}", f"{price:.8f}", "详情" )) # 更新统计信息 self.stats_label.config(text=f"库存总价值: {total_value:.2f} 元 库存总重: {total_weight:.2f} kg") except Exception as e: messagebox.showerror("错误", f"加载产品数据失败: {str(e)}") def load_transactions(self, product_name): """加载指定产品的交易记录""" try: with open("inventory_data.json", "r", encoding="utf-8") as f: data = json.load(f) # 清空现有数据 for item in self.transaction_tree.get_children(): self.transaction_tree.delete(item) # 筛选该产品的交易记录 for transaction in data["transactions"]: if transaction.get("is_snapshot"): continue if transaction["product"] == product_name: txn_type = transaction["type"] weight = float(transaction["weight"]) price = float(transaction["price"]) total_price = weight * price # 设置颜色 tag = "inbound" if txn_type == "入库" else "outbound" item = self.transaction_tree.insert("", tk.END, values=( transaction["time"], txn_type, f"{weight:.2f}", f"{price:.2f}", f"{total_price:.2f}", transaction.get("note", ""), "删除" ), tags=(tag,)) # 设置标签颜色 self.transaction_tree.tag_configure("inbound", background="lightgreen") self.transaction_tree.tag_configure("outbound", background="lightcoral") except Exception as e: messagebox.showerror("错误", f"加载交易记录失败: {str(e)}") def on_product_select(self, event): """产品选择事件""" selection = self.product_tree.selection() if selection: item = self.product_tree.item(selection[0]) product_name = item["values"][0] self.create_product_detail_page(product_name) def show_add_product_page(self): """显示添加产品页面""" self.create_add_product_page() def show_query_page(self): """显示查询页面""" self.create_query_page() def show_inbound_dialog(self): """显示入库对话框""" dialog = tk.Toplevel(self.root) dialog.title("入库") dialog.geometry("350x350") dialog.resizable(False, False) # 居中显示 dialog.transient(self.root) dialog.grab_set() # 计算居中位置 x = (dialog.winfo_screenwidth() // 2) - (350 // 2) y = (dialog.winfo_screenheight() // 2) - (350 // 2) dialog.geometry(f"350x350+{x}+{y}") tk.Label(dialog, text=f"产品: {self.current_product}", font=("Arial", 12, "bold")).pack(pady=15) tk.Label(dialog, text="重量(kg):").pack() weight_entry = tk.Entry(dialog, width=20) weight_entry.pack(pady=5) tk.Label(dialog, text="单价(元/kg):").pack() price_entry = tk.Entry(dialog, width=20) price_entry.pack(pady=5) tk.Label(dialog, text="日期(YYYY-MM-DD):").pack() date_entry = tk.Entry(dialog, width=20) date_entry.insert(0, datetime.now().strftime("%Y-%m-%d")) # 默认今天日期 date_entry.pack(pady=5) tk.Label(dialog, text="备注:").pack() note_entry = tk.Entry(dialog, width=20) note_entry.pack(pady=5) # 设置回车键切换输入框 def focus_next_entry(event, next_entry): next_entry.focus_set() return "break" def confirm_on_enter(event): confirm_inbound() return "break" weight_entry.bind("", lambda e: focus_next_entry(e, price_entry)) price_entry.bind("", lambda e: focus_next_entry(e, date_entry)) date_entry.bind("", lambda e: focus_next_entry(e, note_entry)) note_entry.bind("", confirm_on_enter) # 默认焦点在重量输入框 weight_entry.focus_set() def confirm_inbound(): try: weight = float(weight_entry.get()) price = float(price_entry.get()) note = note_entry.get() date_str = date_entry.get().strip() # 验证日期格式 try: datetime.strptime(date_str, "%Y-%m-%d") except ValueError: messagebox.showerror("错误", "日期格式不正确,请使用YYYY-MM-DD格式") return transaction = { "product": self.current_product, "type": "入库", "weight": f"{weight:.8f}", "price": f"{price:.8f}", "note": note, "time": date_str } # 处理交易记录,支持进度回调 def progress_callback(current, total, message): # 简单的进度显示,可以后续扩展为进度条 print(f"进度: {current}/{total} - {message}") result = self.manager.process_transaction(transaction, progress_callback) if result["success"]: if result["recalculated_count"] > 0: messagebox.showinfo("成功", f"入库记录添加成功!\n重计算了{result['recalculated_count']}条记录") else: messagebox.showinfo("成功", "入库记录添加成功!") else: messagebox.showerror("错误", f"入库失败:{result['message']}") return dialog.destroy() self.create_product_detail_page(self.current_product) except ValueError: messagebox.showerror("错误", "请输入有效的数字") except Exception as e: messagebox.showerror("错误", f"入库失败: {str(e)}") # 按钮框架 button_frame = tk.Frame(dialog) button_frame.pack(pady=20) tk.Button(button_frame, text="确认", command=confirm_inbound, bg="lightgreen", width=8).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="取消", command=dialog.destroy, width=8).pack(side=tk.LEFT, padx=5) def show_outbound_dialog(self): """显示出库对话框""" dialog = tk.Toplevel(self.root) dialog.title("出库") dialog.geometry("350x300") dialog.resizable(False, False) # 居中显示 dialog.transient(self.root) dialog.grab_set() # 计算居中位置 x = (dialog.winfo_screenwidth() // 2) - (350 // 2) y = (dialog.winfo_screenheight() // 2) - (300 // 2) dialog.geometry(f"350x300+{x}+{y}") tk.Label(dialog, text=f"产品: {self.current_product}", font=("Arial", 12, "bold")).pack(pady=15) tk.Label(dialog, text="重量(kg):").pack() weight_entry = tk.Entry(dialog, width=20) weight_entry.pack(pady=5) tk.Label(dialog, text="日期(YYYY-MM-DD):").pack() date_entry = tk.Entry(dialog, width=20) date_entry.insert(0, datetime.now().strftime("%Y-%m-%d")) # 默认今天日期 date_entry.pack(pady=5) tk.Label(dialog, text="备注:").pack() note_entry = tk.Entry(dialog, width=20) note_entry.pack(pady=5) # 设置回车键切换输入框 def focus_next_entry(event, next_entry): next_entry.focus_set() return "break" def confirm_on_enter(event): confirm_outbound() return "break" weight_entry.bind("", lambda e: focus_next_entry(e, date_entry)) date_entry.bind("", lambda e: focus_next_entry(e, note_entry)) note_entry.bind("", confirm_on_enter) # 默认焦点在重量输入框 weight_entry.focus_set() def confirm_outbound(): try: weight = float(weight_entry.get()) note = note_entry.get() date_str = date_entry.get().strip() # 验证日期格式 try: datetime.strptime(date_str, "%Y-%m-%d") except ValueError: messagebox.showerror("错误", "日期格式不正确,请使用YYYY-MM-DD格式") return # 出库记录,价格将在process_transaction中自动计算 transaction = { "product": self.current_product, "type": "出库", "weight": f"{weight:.8f}", "note": note, "time": date_str } # 处理交易记录,支持进度回调 def progress_callback(current, total, message): # 简单的进度显示,可以后续扩展为进度条 print(f"进度: {current}/{total} - {message}") result = self.manager.process_transaction(transaction, progress_callback) if result["success"]: if result["recalculated_count"] > 0: messagebox.showinfo("成功", f"出库记录添加成功!\n重计算了{result['recalculated_count']}条记录") else: messagebox.showinfo("成功", "出库记录添加成功!") else: if result['message'] == "库存不足": messagebox.showerror("错误", "库存不足,无法出库!") else: messagebox.showerror("错误", f"出库失败:{result['message']}") return dialog.destroy() self.create_product_detail_page(self.current_product) except ValueError: messagebox.showerror("错误", "请输入有效的数字") except Exception as e: messagebox.showerror("错误", f"出库失败: {str(e)}") # 按钮框架 button_frame = tk.Frame(dialog) button_frame.pack(pady=20) tk.Button(button_frame, text="确认", command=confirm_outbound, bg="lightcoral", width=8).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="取消", command=dialog.destroy, width=8).pack(side=tk.LEFT, padx=5) def add_product(self): """添加新产品""" name = self.product_name_entry.get().strip() desc = self.product_desc_entry.get().strip() if not name: messagebox.showerror("错误", "产品名称不能为空") return try: with open("inventory_data.json", "r+", encoding="utf-8") as f: data = json.load(f) if name in data["products"]: messagebox.showerror("错误", "产品已存在") return data["products"][name] = { "total_weight": "0.00000000", "avg_price": "0.00000000" } f.seek(0) json.dump(data, f, indent=2, ensure_ascii=False) f.truncate() messagebox.showinfo("成功", "产品添加成功") self.create_main_page() except Exception as e: messagebox.showerror("错误", f"添加产品失败: {str(e)}") def search_products(self): """搜索产品""" keyword = self.search_entry.get().strip() if not keyword: self.load_products() # 如果没有关键词,显示所有产品 return try: with open("inventory_data.json", "r", encoding="utf-8") as f: data = json.load(f) # 清空现有数据 for item in self.product_tree.get_children(): self.product_tree.delete(item) total_value = 0 total_weight = 0 found_count = 0 # 过滤产品 for product_name, product_data in data["products"].items(): if keyword.lower() in product_name.lower(): weight = float(product_data["total_weight"]) price = float(product_data["avg_price"]) value = weight * price total_weight += weight total_value += value found_count += 1 self.product_tree.insert("", tk.END, values=( product_name, f"{weight:.2f}", f"{price:.2f}", "详情" )) # 更新统计信息 self.stats_label.config(text=f"搜索结果: {found_count}个产品 库存总价值: {total_value:.2f} 元 库存总重: {total_weight:.2f} kg") if found_count == 0: messagebox.showinfo("搜索结果", f"没有找到包含'{keyword}'的产品") except Exception as e: messagebox.showerror("错误", f"搜索失败: {str(e)}") def clear_search(self): """清空搜索""" self.search_entry.delete(0, tk.END) self.load_products() def delete_product(self): """删除产品""" if messagebox.askyesno("确认", f"确定要删除产品 {self.current_product} 吗?\n\n注意:删除产品将同时删除该产品的所有交易记录!\n如果有大量交易记录,重计算可能需要一些时间。"): try: # 进度回调函数 def progress_callback(current, total, message): print(f"删除产品进度: {current}/{total} - {message}") # 使用manager的删除产品方法 result = self.manager.delete_product(self.current_product, progress_callback) if result["success"]: if result["deleted_transactions"] > 0: messagebox.showinfo("成功", f"产品删除成功!\n删除了{result['deleted_transactions']}条相关交易记录") else: messagebox.showinfo("成功", "产品删除成功!") self.create_main_page() else: messagebox.showerror("错误", f"删除产品失败:{result['message']}") except Exception as e: messagebox.showerror("错误", f"删除产品失败: {str(e)}") def delete_transaction(self, event): """删除交易记录""" selection = self.transaction_tree.selection() if selection: # 获取选中的交易记录信息 item = self.transaction_tree.item(selection[0]) values = item["values"] if len(values) < 6: messagebox.showerror("错误", "无法获取交易记录信息") return transaction_time = values[0] transaction_type = values[1] weight = float(values[2]) if messagebox.askyesno("确认", f"确定要删除这条记录吗?\n\n时间: {transaction_time}\n类型: {transaction_type}\n重量: {weight}kg\n\n注意:删除记录将触发重计算,可能需要一些时间。"): try: # 查找交易记录的索引 transaction_index = self.manager.find_transaction_index( self.current_product, transaction_time, transaction_type, weight ) if transaction_index == -1: messagebox.showerror("错误", "未找到对应的交易记录") return # 进度回调函数 def progress_callback(current, total, message): print(f"删除进度: {current}/{total} - {message}") # 删除交易记录 result = self.manager.delete_transaction(transaction_index, progress_callback) if result["success"]: if result["recalculated_count"] > 0: messagebox.showinfo("成功", f"交易记录删除成功!\n重计算了{result['recalculated_count']}条记录") else: messagebox.showinfo("成功", "交易记录删除成功!") # 刷新页面 self.create_product_detail_page(self.current_product) else: messagebox.showerror("错误", f"删除失败:{result['message']}") except Exception as e: messagebox.showerror("错误", f"删除交易记录失败: {str(e)}") def execute_query(self): """执行查询""" try: with open("inventory_data.json", "r", encoding="utf-8") as f: data = json.load(f) # 获取查询条件 product_name = self.query_product_entry.get().strip() transaction_type = self.query_type_var.get() start_date = self.start_date_entry.get().strip() end_date = self.end_date_entry.get().strip() min_weight = self.min_weight_entry.get().strip() max_weight = self.max_weight_entry.get().strip() note_keyword = self.note_keyword_entry.get().strip() # 过滤交易记录 self.filtered_transactions = [] for transaction in data["transactions"]: if transaction.get("is_snapshot"): continue # 应用过滤条件 if self._match_query_conditions(transaction, product_name, transaction_type, start_date, end_date, min_weight, max_weight, note_keyword): self.filtered_transactions.append(transaction) # 按时间倒序排序(最新的在前面) self.filtered_transactions.sort(key=lambda x: x["time"], reverse=True) self.total_records = len(self.filtered_transactions) self.current_page = 1 # 重置到第一页 # 显示当前页的数据 self._display_current_page() if self.total_records == 0: messagebox.showinfo("查询结果", "没有找到符合条件的记录") except Exception as e: messagebox.showerror("错误", f"查询失败: {str(e)}") def _display_current_page(self): """显示当前页的数据""" # 清空现有数据 for item in self.query_tree.get_children(): self.query_tree.delete(item) # 计算分页 start_index = (self.current_page - 1) * self.page_size end_index = min(start_index + self.page_size, self.total_records) total_weight = 0 total_value = 0 # 显示当前页的记录 for i in range(start_index, end_index): transaction = self.filtered_transactions[i] weight = float(transaction["weight"]) price = float(transaction["price"]) total_price = weight * price # 计算统计信息(入库为正,出库为负) if transaction["type"] == "入库": total_weight += weight else: total_weight -= weight total_value += total_price # 设置行颜色 tag = "inbound" if transaction["type"] == "入库" else "outbound" self.query_tree.insert("", tk.END, values=( transaction["time"], transaction["product"], transaction["type"], f"{weight:.2f}", f"{price:.2f}", f"{total_price:.2f}", transaction.get("note", "") ), tags=(tag,)) # 设置标签颜色 self.query_tree.tag_configure("inbound", background="lightgreen") self.query_tree.tag_configure("outbound", background="lightcoral") # 更新分页信息 total_pages = (self.total_records + self.page_size - 1) // self.page_size if self.total_records > 0 else 1 self.page_info_label.config(text=f"第 {self.current_page} 页 / 共 {total_pages} 页 (共 {self.total_records} 条记录)") # 更新按钮状态 self.prev_button.config(state=tk.NORMAL if self.current_page > 1 else tk.DISABLED) self.next_button.config(state=tk.NORMAL if self.current_page < total_pages else tk.DISABLED) # 计算全部记录的统计信息 all_total_weight = 0 all_total_value = 0 for transaction in self.filtered_transactions: weight = float(transaction["weight"]) price = float(transaction["price"]) total_price = weight * price if transaction["type"] == "入库": all_total_weight += weight else: all_total_weight -= weight all_total_value += total_price # 更新统计信息 self.query_stats_label.config(text=f"查询结果: {self.total_records}条记录 净重量: {all_total_weight:.2f}kg 总价值: {all_total_value:.2f}元") def prev_page(self): """上一页""" if self.current_page > 1: self.current_page -= 1 self._display_current_page() def next_page(self): """下一页""" total_pages = (self.total_records + self.page_size - 1) // self.page_size if self.total_records > 0 else 1 if self.current_page < total_pages: self.current_page += 1 self._display_current_page() def on_page_size_change(self, event): """每页显示数量改变""" try: self.page_size = int(self.page_size_var.get()) self.current_page = 1 # 重置到第一页 if hasattr(self, 'filtered_transactions') and self.filtered_transactions: self._display_current_page() except ValueError: pass def quick_query(self, period): """快速查询""" from datetime import datetime, timedelta # 清空查询条件 self.clear_query_conditions() today = datetime.now() if period == "today": # 今日 date_str = today.strftime("%Y-%m-%d") self.start_date_entry.delete(0, tk.END) self.start_date_entry.insert(0, date_str) self.end_date_entry.delete(0, tk.END) self.end_date_entry.insert(0, date_str) elif period == "week": # 本周(周一到今天) days_since_monday = today.weekday() monday = today - timedelta(days=days_since_monday) self.start_date_entry.delete(0, tk.END) self.start_date_entry.insert(0, monday.strftime("%Y-%m-%d")) self.end_date_entry.delete(0, tk.END) self.end_date_entry.insert(0, today.strftime("%Y-%m-%d")) elif period == "month": # 本月(月初到今天) month_start = today.replace(day=1) self.start_date_entry.delete(0, tk.END) self.start_date_entry.insert(0, month_start.strftime("%Y-%m-%d")) self.end_date_entry.delete(0, tk.END) self.end_date_entry.insert(0, today.strftime("%Y-%m-%d")) elif period == "all": # 全部记录 self.start_date_entry.delete(0, tk.END) self.start_date_entry.insert(0, "2020-01-01") self.end_date_entry.delete(0, tk.END) self.end_date_entry.insert(0, "2030-12-31") # 执行查询 self.execute_query() def clear_query_conditions(self): """清空查询条件""" self.query_product_entry.delete(0, tk.END) self.query_type_var.set("全部") self.start_date_entry.delete(0, tk.END) self.start_date_entry.insert(0, "2025-06-26") self.end_date_entry.delete(0, tk.END) self.end_date_entry.insert(0, "2025-07-26") self.min_weight_entry.delete(0, tk.END) self.max_weight_entry.delete(0, tk.END) self.note_keyword_entry.delete(0, tk.END) def _match_query_conditions(self, transaction, product_name, transaction_type, start_date, end_date, min_weight, max_weight, note_keyword): """检查交易记录是否匹配查询条件""" from datetime import datetime # 产品名称过滤 if product_name and product_name.lower() not in transaction["product"].lower(): return False # 交易类型过滤 if transaction_type != "全部" and transaction["type"] != transaction_type: return False # 时间范围过滤 try: if start_date: start_dt = datetime.strptime(start_date, "%Y-%m-%d") txn_dt = datetime.strptime(transaction["time"].split()[0], "%Y-%m-%d") if txn_dt < start_dt: return False except ValueError: pass # 忽略日期格式错误 try: if end_date: end_dt = datetime.strptime(end_date, "%Y-%m-%d") txn_dt = datetime.strptime(transaction["time"].split()[0], "%Y-%m-%d") if txn_dt > end_dt: return False except ValueError: pass # 忽略日期格式错误 # 重量范围过滤 weight = float(transaction["weight"]) try: if min_weight and weight < float(min_weight): return False except ValueError: pass try: if max_weight and weight > float(max_weight): return False except ValueError: pass # 备注关键词过滤 if note_keyword: note = transaction.get("note", "") if note_keyword.lower() not in note.lower(): return False return True def print_query_results(self): """打印查询结果""" try: # 获取当前查询结果 results = [] for item in self.query_tree.get_children(): values = self.query_tree.item(item)["values"] results.append(values) if not results: messagebox.showwarning("提示", "没有查询结果可以打印") return # 创建打印预览窗口 print_window = tk.Toplevel(self.root) print_window.title("打印预览") print_window.geometry("800x600") print_window.resizable(True, True) # 创建文本框显示打印内容 text_frame = tk.Frame(print_window) text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) text_widget = tk.Text(text_frame, wrap=tk.NONE, font=("Courier", 10)) scrollbar_y = tk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text_widget.yview) scrollbar_x = tk.Scrollbar(text_frame, orient=tk.HORIZONTAL, command=text_widget.xview) text_widget.config(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set) scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) text_widget.pack(fill=tk.BOTH, expand=True) # 生成打印内容 from datetime import datetime current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print_content = f"""库存管理系统 - 查询结果报表 生成时间: {current_time} {'='*80} """ # 表头 header = f"{'时间':<20} {'产品名':<15} {'类型':<8} {'重量(kg)':<10} {'价格(元/kg)':<12} {'总价(元)':<12} {'备注':<20}" print_content += header + "\n" print_content += "-" * 80 + "\n" # 数据行 for values in results: line = f"{values[0]:<20} {values[1]:<15} {values[2]:<8} {values[3]:<10} {values[4]:<12} {values[5]:<12} {values[6]:<20}" print_content += line + "\n" # 统计信息 stats_text = self.query_stats_label.cget("text") print_content += "\n" + "="*80 + "\n" print_content += f"统计信息: {stats_text}\n" print_content += f"记录总数: {len(results)}条\n" text_widget.insert(tk.END, print_content) text_widget.config(state=tk.DISABLED) # 按钮框架 button_frame = tk.Frame(print_window) button_frame.pack(pady=10) def copy_to_clipboard(): print_window.clipboard_clear() print_window.clipboard_append(print_content) messagebox.showinfo("成功", "内容已复制到剪贴板") tk.Button(button_frame, text="复制到剪贴板", command=copy_to_clipboard, bg="lightblue").pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="关闭", command=print_window.destroy).pack(side=tk.LEFT, padx=5) except Exception as e: messagebox.showerror("错误", f"生成打印预览失败: {str(e)}") def run(self): """运行应用""" self.root.mainloop() if __name__ == "__main__": app = InventoryUI() app.run()