#本产品含AI量高达98% import tkinter as tk from tkinter import ttk, messagebox from datetime import datetime import json import os class InventoryApp: def __init__(self, root): self.root = root self.root.title("出入库管理系统") self.root.geometry("800x600") # 数据存储 self.data_file = "inventory_data.json" self.load_data() # 当前页面状态 self.current_page = "main" self.current_product = None self.current_main_page = 1 self.current_detail_page = 1 self.current_query_page = 1 self.items_per_page = 14 self.query_results = [] # 窗口大小相关 self.last_window_height = 600 # 创建主框架 self.main_frame = tk.Frame(root) self.main_frame.pack(fill=tk.BOTH, expand=True) # 绑定窗口大小变化事件 self.root.bind('', self.on_window_resize) # 显示主页面 self.show_main_page() def load_data(self): """加载数据""" if os.path.exists(self.data_file): try: with open(self.data_file, 'r', encoding='utf-8') as f: self.data = json.load(f) except: self.data = {"products": {}, "transactions": []} else: # 初始化空数据结构 self.data = {"products": {}, "transactions": []} def save_data(self): """保存数据""" with open(self.data_file, 'w', encoding='utf-8') as f: json.dump(self.data, f, ensure_ascii=False, indent=2) def clear_frame(self): """清空当前框架""" for widget in self.main_frame.winfo_children(): widget.destroy() def calculate_items_per_page(self): """根据窗口高度动态计算每页显示的条目数""" window_height = self.root.winfo_height() # 减去顶部搜索区域(约60px)、表头(约30px)、底部分页区域(约50px)的高度 available_height = window_height - 140 # 每个条目大约占用35px高度 item_height = 35 # 计算可显示的条目数,最少5个,最多20个 items_count = max(5, min(20, available_height // item_height)) return items_count def on_window_resize(self, event): """窗口大小变化时的处理""" # 只处理主窗口的大小变化事件 if event.widget == self.root: current_height = self.root.winfo_height() # 如果高度变化超过50px,重新计算每页条目数 if abs(current_height - self.last_window_height) > 50: self.last_window_height = current_height new_items_per_page = self.calculate_items_per_page() if new_items_per_page != self.items_per_page: self.items_per_page = new_items_per_page # 重新刷新当前页面的列表 if self.current_page == "main": self.update_product_list() # 更新页码标签 total_products = len(self.data["products"]) max_pages = (total_products + self.items_per_page - 1) // self.items_per_page if self.current_main_page > max_pages and max_pages > 0: self.current_main_page = max_pages self.main_page_label.config(text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页") elif self.current_page == "detail": self.update_detail_list() # 更新页码标签 product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] max_pages = (len(product_transactions) + self.items_per_page - 1) // self.items_per_page if self.current_detail_page > max_pages and max_pages > 0: self.current_detail_page = max_pages self.detail_page_label.config(text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") def show_main_page(self): """显示主页面""" self.current_page = "main" self.clear_frame() # 如果是首次显示主页面,保持默认的14条,否则根据窗口大小计算 if not hasattr(self, 'main_page_initialized'): self.main_page_initialized = True else: self.items_per_page = self.calculate_items_per_page() # 顶部搜索功能和添加产品按钮 search_frame = tk.Frame(self.main_frame) search_frame.pack(fill=tk.X, padx=10, pady=10) tk.Label(search_frame, text="搜索产品:", font=("Arial", 12)).pack(side=tk.LEFT) self.search_var = tk.StringVar() search_entry = tk.Entry(search_frame, textvariable=self.search_var, font=("Arial", 12)) search_entry.pack(side=tk.LEFT, padx=(10, 5), fill=tk.X, expand=True) 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_category_page, bg="lightblue", font=("Arial", 12)).pack(side=tk.LEFT, padx=5) tk.Button(search_frame, text="查询", command=self.show_query_page, bg="lightcyan", font=("Arial", 12)).pack(side=tk.LEFT, padx=5) # 产品列表 list_frame = tk.Frame(self.main_frame) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表头 header_frame = tk.Frame(list_frame) header_frame.pack(fill=tk.X) tk.Label(header_frame, text="产品名", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="当前剩余重量(kg)", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="平均价格(元/kg)", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="操作", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) # 产品条目 self.product_list_frame = tk.Frame(list_frame) self.product_list_frame.pack(fill=tk.BOTH, expand=True) self.update_product_list() # 页数控制 page_frame = tk.Frame(self.main_frame) page_frame.pack(fill=tk.X, padx=10, pady=10) tk.Button(page_frame, text="上一页", command=self.prev_main_page).pack(side=tk.LEFT) total_products = len(self.data["products"]) max_pages = max(1, (total_products + self.items_per_page - 1) // self.items_per_page) self.main_page_label = tk.Label(page_frame, text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页") self.main_page_label.pack(side=tk.LEFT, padx=20) tk.Button(page_frame, text="下一页", command=self.next_main_page).pack(side=tk.LEFT) def search_products(self): """搜索产品""" # 重置到第一页 self.current_main_page = 1 self.update_product_list() def clear_search(self): """清空搜索""" self.search_var.set("") self.current_main_page = 1 self.update_product_list() def update_product_list(self): """更新产品列表""" for widget in self.product_list_frame.winfo_children(): widget.destroy() # 获取搜索词并过滤产品 search_term = self.search_var.get().strip().lower() all_products = list(self.data["products"].items()) if search_term: # 模糊搜索:产品名包含搜索词 filtered_products = [(name, data) for name, data in all_products if search_term in name.lower()] else: filtered_products = all_products # 分页处理 start_idx = (self.current_main_page - 1) * self.items_per_page end_idx = start_idx + self.items_per_page page_products = filtered_products[start_idx:end_idx] # 显示产品列表 for product_name, product_data in page_products: row_frame = tk.Frame(self.product_list_frame) row_frame.pack(fill=tk.X, pady=1) tk.Label(row_frame, text=product_name, width=21, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{product_data['total_weight']:.1f}", width=22, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{product_data['avg_price']:.2f}", width=21, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Button(row_frame, text="详情", width=21, command=lambda p=product_name: self.show_detail_page(p)).pack(side=tk.LEFT) # 更新分页信息 total_products = len(filtered_products) max_pages = max(1, (total_products + self.items_per_page - 1) // self.items_per_page) if hasattr(self, 'main_page_label') and self.main_page_label.winfo_exists(): try: if search_term: self.main_page_label.config(text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页 (找到 {total_products} 个结果)") else: self.main_page_label.config(text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页") except tk.TclError: pass # 忽略已销毁的组件错误 # 如果没有搜索结果,显示提示 if not page_products and search_term: no_result_frame = tk.Frame(self.product_list_frame) no_result_frame.pack(fill=tk.X, pady=20) tk.Label(no_result_frame, text=f"未找到包含 '{search_term}' 的产品", font=("Arial", 12), fg="gray").pack() def prev_main_page(self): """上一页""" if self.current_main_page > 1: self.current_main_page -= 1 self.update_product_list() def next_main_page(self): """下一页""" # 获取当前过滤后的产品数量 search_term = self.search_var.get().strip().lower() all_products = list(self.data["products"].items()) if search_term: filtered_products = [(name, data) for name, data in all_products if search_term in name.lower()] else: filtered_products = all_products total_products = len(filtered_products) max_pages = max(1, (total_products + self.items_per_page - 1) // self.items_per_page) if self.current_main_page < max_pages: self.current_main_page += 1 self.update_product_list() def show_detail_page(self, product_name): """显示详细条目页面""" self.current_page = "detail" self.current_product = product_name self.current_detail_page = 1 self.clear_frame() # 如果是首次显示详情页面,保持默认的14条,否则根据窗口大小计算 if not hasattr(self, 'detail_page_initialized'): self.detail_page_initialized = True else: self.items_per_page = self.calculate_items_per_page() # 顶部产品名和按钮 top_frame = tk.Frame(self.main_frame) top_frame.pack(fill=tk.X, padx=10, pady=10) tk.Label(top_frame, text=f"产品: {product_name}", font=("Arial", 16, "bold")).pack(side=tk.LEFT) button_frame = tk.Frame(top_frame) button_frame.pack(side=tk.RIGHT) tk.Button(button_frame, text="入库", command=self.show_inbound_page, bg="lightgreen").pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="出库", command=self.show_outbound_page, bg="lightcoral").pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="删除产品", command=self.delete_product_category, bg="darkred", fg="white").pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="返回主页", command=self.show_main_page).pack(side=tk.LEFT, padx=5) # 详细条目列表 detail_frame = tk.Frame(self.main_frame) detail_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表头 header_frame = tk.Frame(detail_frame) header_frame.pack(fill=tk.X) tk.Label(header_frame, text="时间", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="类型", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="重量(kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="价格(元/kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="总价(元)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="备注", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="操作", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) # 交易记录 self.detail_list_frame = tk.Frame(detail_frame) self.detail_list_frame.pack(fill=tk.BOTH, expand=True) self.update_detail_list() # 页数控制 page_frame = tk.Frame(self.main_frame) page_frame.pack(fill=tk.X, padx=10, pady=10) tk.Button(page_frame, text="上一页", command=self.prev_detail_page).pack(side=tk.LEFT) product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] max_pages = max(1, (len(product_transactions) + self.items_per_page - 1) // self.items_per_page) self.detail_page_label = tk.Label(page_frame, text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") self.detail_page_label.pack(side=tk.LEFT, padx=20) tk.Button(page_frame, text="下一页", command=self.next_detail_page).pack(side=tk.LEFT) def update_detail_list(self): """更新详细条目列表""" for widget in self.detail_list_frame.winfo_children(): widget.destroy() # 筛选当前产品的交易记录 product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] product_transactions.sort(key=lambda x: x["time"], reverse=True) start_idx = (self.current_detail_page - 1) * self.items_per_page end_idx = start_idx + self.items_per_page page_transactions = product_transactions[start_idx:end_idx] for i, transaction in enumerate(page_transactions): row_frame = tk.Frame(self.detail_list_frame) row_frame.pack(fill=tk.X, pady=1) total_price = transaction['weight'] * transaction['price'] tk.Label(row_frame, text=transaction["time"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) color = "lightgreen" if transaction["type"] == "入库" else "lightcoral" tk.Label(row_frame, text=transaction["type"], width=15, relief=tk.RIDGE, bg=color).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{transaction['weight']:.1f}", width=14, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{transaction['price']:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{total_price:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=transaction["note"], width=14, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Button(row_frame, text="删除", width=15, bg="red", fg="white", command=lambda t=transaction: self.delete_transaction(t)).pack(side=tk.LEFT) def prev_detail_page(self): """上一页""" if self.current_detail_page > 1: self.current_detail_page -= 1 self.update_detail_list() product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] max_pages = max(1, (len(product_transactions) + self.items_per_page - 1) // self.items_per_page) self.detail_page_label.config(text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") def next_detail_page(self): """详情页下一页""" product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] max_pages = (len(product_transactions) + self.items_per_page - 1) // self.items_per_page if self.current_detail_page < max_pages: self.current_detail_page += 1 self.update_detail_list() self.detail_page_label.config(text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") def show_inbound_page(self): """显示入库页面""" self.current_page = "inbound" self.clear_frame() # 标题 title_frame = tk.Frame(self.main_frame) title_frame.pack(fill=tk.X, padx=10, pady=10) tk.Label(title_frame, text=f"入库 - {self.current_product}", font=("Arial", 16, "bold")).pack(side=tk.LEFT) tk.Button(title_frame, text="返回详情", command=lambda: self.show_detail_page(self.current_product)).pack(side=tk.RIGHT) # 输入表单 form_frame = tk.Frame(self.main_frame) form_frame.pack(fill=tk.X, padx=50, pady=50) # 入库重量 tk.Label(form_frame, text="入库重量(kg):", font=("Arial", 12)).grid(row=0, column=0, sticky="w", pady=10) self.inbound_weight_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.inbound_weight_var, font=("Arial", 12), width=20).grid(row=0, column=1, padx=10, pady=10) # 入库价格 tk.Label(form_frame, text="入库价格(元/kg):", font=("Arial", 12)).grid(row=1, column=0, sticky="w", pady=10) self.inbound_price_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.inbound_price_var, font=("Arial", 12), width=20).grid(row=1, column=1, padx=10, pady=10) # 入库日期 tk.Label(form_frame, text="入库日期:", font=("Arial", 12)).grid(row=2, column=0, sticky="w", pady=10) self.inbound_date_var = tk.StringVar(value=datetime.now().strftime("%Y-%m-%d")) date_entry = tk.Entry(form_frame, textvariable=self.inbound_date_var, font=("Arial", 12), width=20) date_entry.grid(row=2, column=1, padx=10, pady=10) tk.Label(form_frame, text="(格式: 2024-01-01)", font=("Arial", 8), fg="gray").grid(row=2, column=2, sticky="w", padx=5) # 备注 tk.Label(form_frame, text="备注:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", pady=10) self.inbound_note_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.inbound_note_var, font=("Arial", 12), width=20).grid(row=3, column=1, padx=10, pady=10) # 提交按钮 button_frame = tk.Frame(form_frame) button_frame.grid(row=4, column=0, columnspan=2, pady=20) tk.Button(button_frame, text="确认入库", command=self.process_inbound, bg="lightgreen", font=("Arial", 12)).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="取消", command=lambda: self.show_detail_page(self.current_product), font=("Arial", 12)).pack(side=tk.LEFT, padx=10) def show_outbound_page(self): """显示出库页面""" self.current_page = "outbound" self.clear_frame() # 标题 title_frame = tk.Frame(self.main_frame) title_frame.pack(fill=tk.X, padx=10, pady=10) tk.Label(title_frame, text=f"出库 - {self.current_product}", font=("Arial", 16, "bold")).pack(side=tk.LEFT) tk.Button(title_frame, text="返回详情", command=lambda: self.show_detail_page(self.current_product)).pack(side=tk.RIGHT) # 输入表单 form_frame = tk.Frame(self.main_frame) form_frame.pack(fill=tk.X, padx=50, pady=50) # 出库重量 tk.Label(form_frame, text="出库重量(kg):", font=("Arial", 12)).grid(row=0, column=0, sticky="w", pady=10) self.outbound_weight_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.outbound_weight_var, font=("Arial", 12), width=20).grid(row=0, column=1, padx=10, pady=10) # 出库价格(自动计算) tk.Label(form_frame, text="出库价格(元/kg):", font=("Arial", 12)).grid(row=1, column=0, sticky="w", pady=10) current_price = self.data["products"][self.current_product]["avg_price"] self.outbound_price_var = tk.StringVar(value=str(current_price)) price_entry = tk.Entry(form_frame, textvariable=self.outbound_price_var, font=("Arial", 12), width=20, state="readonly") price_entry.grid(row=1, column=1, padx=10, pady=10) # 出库日期 tk.Label(form_frame, text="出库日期:", font=("Arial", 12)).grid(row=2, column=0, sticky="w", pady=10) self.outbound_date_var = tk.StringVar(value=datetime.now().strftime("%Y-%m-%d")) date_entry = tk.Entry(form_frame, textvariable=self.outbound_date_var, font=("Arial", 12), width=20) date_entry.grid(row=2, column=1, padx=10, pady=10) tk.Label(form_frame, text="(格式: 2024-01-01)", font=("Arial", 8), fg="gray").grid(row=2, column=2, sticky="w", padx=5) # 备注 tk.Label(form_frame, text="备注:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", pady=10) self.outbound_note_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.outbound_note_var, font=("Arial", 12), width=20).grid(row=3, column=1, padx=10, pady=10) # 提交按钮 button_frame = tk.Frame(form_frame) button_frame.grid(row=4, column=0, columnspan=2, pady=20) tk.Button(button_frame, text="确认出库", command=self.process_outbound, bg="lightcoral", font=("Arial", 12)).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="取消", command=lambda: self.show_detail_page(self.current_product), font=("Arial", 12)).pack(side=tk.LEFT, padx=10) def process_inbound(self): """处理入库""" try: weight = float(self.inbound_weight_var.get()) price = float(self.inbound_price_var.get()) note = self.inbound_note_var.get().strip() date_str = self.inbound_date_var.get().strip() if weight <= 0 or price <= 0: messagebox.showerror("错误", "重量和价格必须大于0") return # 验证日期格式 try: datetime.strptime(date_str, "%Y-%m-%d") except ValueError: messagebox.showerror("错误", "请输入正确的日期格式 (YYYY-MM-DD)") return # 更新产品数据 if self.current_product not in self.data["products"]: self.data["products"][self.current_product] = {"total_weight": 0, "avg_price": 0} product_data = self.data["products"][self.current_product] old_total_weight = product_data["total_weight"] old_avg_price = product_data["avg_price"] # 计算新的平均价格 new_total_weight = old_total_weight + weight new_avg_price = (old_total_weight * old_avg_price + weight * price) / new_total_weight product_data["total_weight"] = new_total_weight product_data["avg_price"] = new_avg_price # 添加交易记录 transaction = { "product": self.current_product, "type": "入库", "weight": weight, "price": price, "note": note, "time": date_str } self.data["transactions"].append(transaction) # 保存数据 self.save_data() messagebox.showinfo("成功", "入库成功!") self.show_detail_page(self.current_product) except ValueError: messagebox.showerror("错误", "请输入有效的数字") def process_outbound(self): """处理出库""" try: weight = float(self.outbound_weight_var.get()) price = float(self.outbound_price_var.get()) note = self.outbound_note_var.get().strip() date_str = self.outbound_date_var.get().strip() if weight <= 0: messagebox.showerror("错误", "重量必须大于0") return # 验证日期格式 try: datetime.strptime(date_str, "%Y-%m-%d") except ValueError: messagebox.showerror("错误", "请输入正确的日期格式 (YYYY-MM-DD)") return # 检查库存是否足够 current_weight = self.data["products"][self.current_product]["total_weight"] if weight > current_weight: messagebox.showerror("错误", f"库存不足!当前库存: {current_weight:.1f}kg") return # 更新产品数据 self.data["products"][self.current_product]["total_weight"] -= weight # 添加交易记录 transaction = { "product": self.current_product, "type": "出库", "weight": weight, "price": price, "note": note, "time": date_str } self.data["transactions"].append(transaction) # 保存数据 self.save_data() messagebox.showinfo("成功", "出库成功!") self.show_detail_page(self.current_product) except ValueError: messagebox.showerror("错误", "请输入有效的数字") def show_add_category_page(self): """显示添加产品大类页面""" self.current_page = "add_category" self.clear_frame() # 标题 title_frame = tk.Frame(self.main_frame) title_frame.pack(fill=tk.X, padx=10, pady=10) tk.Label(title_frame, text="添加产品大类", font=("Arial", 16, "bold")).pack(side=tk.LEFT) tk.Button(title_frame, text="返回主页", command=self.show_main_page).pack(side=tk.RIGHT) # 输入表单 form_frame = tk.Frame(self.main_frame) form_frame.pack(fill=tk.X, padx=50, pady=50) # 产品名称 tk.Label(form_frame, text="产品名称:", font=("Arial", 12)).grid(row=0, column=0, sticky="w", pady=10) self.category_name_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.category_name_var, font=("Arial", 12), width=30).grid(row=0, column=1, padx=10, pady=10) # 初始重量 tk.Label(form_frame, text="初始重量(kg):", font=("Arial", 12)).grid(row=1, column=0, sticky="w", pady=10) self.category_weight_var = tk.StringVar(value="0") tk.Entry(form_frame, textvariable=self.category_weight_var, font=("Arial", 12), width=30).grid(row=1, column=1, padx=10, pady=10) # 初始价格 tk.Label(form_frame, text="初始价格(元/kg):", font=("Arial", 12)).grid(row=2, column=0, sticky="w", pady=10) self.category_price_var = tk.StringVar(value="0") tk.Entry(form_frame, textvariable=self.category_price_var, font=("Arial", 12), width=30).grid(row=2, column=1, padx=10, pady=10) # 产品描述 tk.Label(form_frame, text="产品描述:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", pady=10) self.category_desc_var = tk.StringVar() tk.Entry(form_frame, textvariable=self.category_desc_var, font=("Arial", 12), width=30).grid(row=3, column=1, padx=10, pady=10) # 提交按钮 button_frame = tk.Frame(form_frame) button_frame.grid(row=4, column=0, columnspan=2, pady=20) tk.Button(button_frame, text="确认添加", command=self.process_add_category, bg="lightblue", font=("Arial", 12)).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="取消", command=self.show_main_page, font=("Arial", 12)).pack(side=tk.LEFT, padx=10) # 说明文字 info_frame = tk.Frame(self.main_frame) info_frame.pack(fill=tk.X, padx=50, pady=10) info_text = "说明:\n• 产品名称不能为空且不能与现有产品重复\n• 初始重量和价格可以设为0,后续通过入库操作添加\n• 产品描述为可选项" tk.Label(info_frame, text=info_text, font=("Arial", 10), justify=tk.LEFT, fg="gray").pack(anchor="w") def process_add_category(self): """处理添加产品大类""" try: name = self.category_name_var.get().strip() weight = float(self.category_weight_var.get()) price = float(self.category_price_var.get()) description = self.category_desc_var.get().strip() # 验证输入 if not name: messagebox.showerror("错误", "产品名称不能为空") return if name in self.data["products"]: messagebox.showerror("错误", f"产品 '{name}' 已存在,请使用其他名称") return if weight < 0 or price < 0: messagebox.showerror("错误", "重量和价格不能为负数") return # 添加新产品 self.data["products"][name] = { "total_weight": weight, "avg_price": price } # 如果有初始重量和价格,添加初始入库记录 if weight > 0 and price > 0: transaction = { "product": name, "type": "入库", "weight": weight, "price": price, "note": f"初始库存 - {description}" if description else "初始库存", "time": datetime.now().strftime("%Y-%m-%d %H:%M") } self.data["transactions"].append(transaction) # 保存数据 self.save_data() messagebox.showinfo("成功", f"产品大类 '{name}' 添加成功!") self.show_main_page() except ValueError: messagebox.showerror("错误", "请输入有效的数字") def show_query_page(self): """显示查询页面""" self.current_page = "query" self.current_query_page = 1 self.clear_frame() # 标题 title_frame = tk.Frame(self.main_frame) title_frame.pack(fill=tk.X, padx=10, pady=10) tk.Label(title_frame, text="订单查询", font=("Arial", 16, "bold")).pack(side=tk.LEFT) tk.Button(title_frame, text="返回主页", command=self.show_main_page).pack(side=tk.RIGHT) # 查询条件 query_frame = tk.Frame(self.main_frame) query_frame.pack(fill=tk.X, padx=10, pady=10) # 第一行:产品名称和交易类型 row1_frame = tk.Frame(query_frame) row1_frame.pack(fill=tk.X, pady=5) tk.Label(row1_frame, text="产品名称:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_product_var = tk.StringVar() tk.Entry(row1_frame, textvariable=self.query_product_var, font=("Arial", 10), width=15).pack(side=tk.LEFT, padx=(5, 20)) tk.Label(row1_frame, text="交易类型:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_type_var = tk.StringVar() type_combo = tk.OptionMenu(row1_frame, self.query_type_var, "全部", "入库", "出库") type_combo.config(font=("Arial", 10)) self.query_type_var.set("全部") type_combo.pack(side=tk.LEFT, padx=5) # 第二行:时间范围 row2_frame = tk.Frame(query_frame) row2_frame.pack(fill=tk.X, pady=5) tk.Label(row2_frame, text="开始时间:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_start_date_var = tk.StringVar() tk.Entry(row2_frame, textvariable=self.query_start_date_var, font=("Arial", 10), width=12).pack(side=tk.LEFT, padx=(5, 10)) tk.Label(row2_frame, text="(格式: 2024-01-01)", font=("Arial", 8), fg="gray").pack(side=tk.LEFT, padx=(0, 20)) tk.Label(row2_frame, text="结束时间:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_end_date_var = tk.StringVar() tk.Entry(row2_frame, textvariable=self.query_end_date_var, font=("Arial", 10), width=12).pack(side=tk.LEFT, padx=(5, 10)) tk.Label(row2_frame, text="(格式: 2024-12-31)", font=("Arial", 8), fg="gray").pack(side=tk.LEFT) # 第三行:重量和价格范围 row3_frame = tk.Frame(query_frame) row3_frame.pack(fill=tk.X, pady=5) tk.Label(row3_frame, text="最小重量:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_min_weight_var = tk.StringVar() tk.Entry(row3_frame, textvariable=self.query_min_weight_var, font=("Arial", 10), width=10).pack(side=tk.LEFT, padx=(5, 20)) tk.Label(row3_frame, text="最大重量:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_max_weight_var = tk.StringVar() tk.Entry(row3_frame, textvariable=self.query_max_weight_var, font=("Arial", 10), width=10).pack(side=tk.LEFT, padx=(5, 20)) tk.Label(row3_frame, text="备注关键词:", font=("Arial", 10)).pack(side=tk.LEFT) self.query_note_var = tk.StringVar() tk.Entry(row3_frame, textvariable=self.query_note_var, font=("Arial", 10), width=15).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="查询", command=self.execute_query, bg="lightgreen", font=("Arial", 12)).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="清空条件", command=self.clear_query_conditions, font=("Arial", 12)).pack(side=tk.LEFT, padx=5) # 查询结果列表 result_frame = tk.Frame(self.main_frame) result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表头 header_frame = tk.Frame(result_frame) header_frame.pack(fill=tk.X) tk.Label(header_frame, text="时间", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="产品名", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="类型", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="重量(kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="价格(元/kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="总价(元)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) tk.Label(header_frame, text="备注", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) # 查询结果 self.query_result_frame = tk.Frame(result_frame) self.query_result_frame.pack(fill=tk.BOTH, expand=True) # 分页控制 self.query_page_frame = tk.Frame(self.main_frame) self.query_page_frame.pack(fill=tk.X, padx=10, pady=10) tk.Button(self.query_page_frame, text="上一页", command=self.prev_query_page).pack(side=tk.LEFT) self.query_page_label = tk.Label(self.query_page_frame, text="第 1 页 / 共 1 页") self.query_page_label.pack(side=tk.LEFT, padx=20) tk.Button(self.query_page_frame, text="下一页", command=self.next_query_page).pack(side=tk.LEFT) # 设置默认查询条件:最近一个月 from datetime import datetime, timedelta today = datetime.now() one_month_ago = today - timedelta(days=30) self.query_start_date_var.set(one_month_ago.strftime("%Y-%m-%d")) self.query_end_date_var.set(today.strftime("%Y-%m-%d")) # 初始显示最近一个月的交易记录 self.execute_query() def execute_query(self): """执行查询""" # 获取查询条件 product_name = self.query_product_var.get().strip().lower() transaction_type = self.query_type_var.get() start_date = self.query_start_date_var.get().strip() end_date = self.query_end_date_var.get().strip() min_weight = self.query_min_weight_var.get().strip() max_weight = self.query_max_weight_var.get().strip() note_keyword = self.query_note_var.get().strip().lower() # 过滤交易记录 filtered_transactions = [] for transaction in self.data["transactions"]: # 产品名称过滤 if product_name and product_name not in transaction["product"].lower(): continue # 交易类型过滤 if transaction_type != "全部" and transaction["type"] != transaction_type: continue # 时间范围过滤 if start_date: try: trans_date = transaction["time"][:10] # 取日期部分 if trans_date < start_date: continue except: pass if end_date: try: trans_date = transaction["time"][:10] # 取日期部分 if trans_date > end_date: continue except: pass # 重量范围过滤 if min_weight: try: if transaction["weight"] < float(min_weight): continue except ValueError: pass if max_weight: try: if transaction["weight"] > float(max_weight): continue except ValueError: pass # 备注关键词过滤 if note_keyword and note_keyword not in transaction["note"].lower(): continue filtered_transactions.append(transaction) # 按时间倒序排列 filtered_transactions.sort(key=lambda x: x["time"], reverse=True) # 保存查询结果 self.query_results = filtered_transactions # 重置到第一页 self.current_query_page = 1 # 更新查询结果显示 self.update_query_results() def update_query_results(self): """更新查询结果显示""" for widget in self.query_result_frame.winfo_children(): widget.destroy() # 分页处理 start_idx = (self.current_query_page - 1) * self.items_per_page end_idx = start_idx + self.items_per_page page_results = self.query_results[start_idx:end_idx] # 显示查询结果 for transaction in page_results: row_frame = tk.Frame(self.query_result_frame) row_frame.pack(fill=tk.X, pady=1) # 计算总价 total_price = transaction["weight"] * transaction["price"] tk.Label(row_frame, text=transaction["time"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=transaction["product"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) color = "lightgreen" if transaction["type"] == "入库" else "lightcoral" tk.Label(row_frame, text=transaction["type"], width=14, relief=tk.RIDGE, bg=color).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{transaction['weight']:.1f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{transaction['price']:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=f"{total_price:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) tk.Label(row_frame, text=transaction["note"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) # 更新分页信息 total_results = len(self.query_results) max_pages = max(1, (total_results + self.items_per_page - 1) // self.items_per_page) self.query_page_label.config(text=f"第 {self.current_query_page} 页 / 共 {max_pages} 页 (共 {total_results} 条记录)") # 计算统计信息 total_weight = 0 total_price = 0 for transaction in self.query_results: total_weight += transaction["weight"] total_price += transaction["weight"] * transaction["price"] # 在分页信息右侧显示统计信息 stats_text = f"总重量: {total_weight:.2f}kg 总价: {total_price:.2f}元" if hasattr(self, 'query_stats_label') and self.query_stats_label.winfo_exists(): try: self.query_stats_label.config(text=stats_text) except tk.TclError: # 如果标签已被销毁,重新创建 self.query_stats_label = tk.Label(self.query_page_frame, text=stats_text, font=("Arial", 10, "bold"), fg="blue") self.query_stats_label.pack(side=tk.RIGHT) else: self.query_stats_label = tk.Label(self.query_page_frame, text=stats_text, font=("Arial", 10, "bold"), fg="blue") self.query_stats_label.pack(side=tk.RIGHT) # 如果没有查询结果,显示提示 if not page_results: no_result_frame = tk.Frame(self.query_result_frame) no_result_frame.pack(fill=tk.X, pady=20) tk.Label(no_result_frame, text="未找到符合条件的记录", font=("Arial", 12), fg="gray").pack() def clear_query_conditions(self): """清空查询条件""" self.query_product_var.set("") self.query_type_var.set("全部") self.query_start_date_var.set("") self.query_end_date_var.set("") self.query_min_weight_var.set("") self.query_max_weight_var.set("") self.query_note_var.set("") self.execute_query() def prev_query_page(self): """查询结果上一页""" if self.current_query_page > 1: self.current_query_page -= 1 self.update_query_results() def next_query_page(self): """查询结果下一页""" total_results = len(self.query_results) max_pages = max(1, (total_results + self.items_per_page - 1) // self.items_per_page) if self.current_query_page < max_pages: self.current_query_page += 1 self.update_query_results() def delete_transaction(self, transaction): """删除交易记录""" # 确认删除 result = messagebox.askyesno("确认删除", f"确定要删除这条交易记录吗?\n\n时间: {transaction['time']}\n类型: {transaction['type']}\n重量: {transaction['weight']:.1f}kg\n价格: {transaction['price']:.2f}元/kg") if not result: return try: # 从交易记录中移除 self.data["transactions"].remove(transaction) # 重新计算产品数据 self.recalculate_product_data(self.current_product) # 保存数据 self.save_data() messagebox.showinfo("成功", "交易记录删除成功!") # 刷新详情页面 self.show_detail_page(self.current_product) except ValueError: messagebox.showerror("错误", "删除失败,请重试") def recalculate_product_data(self, product_name): """重新计算产品数据""" # 获取该产品的所有交易记录 product_transactions = [t for t in self.data["transactions"] if t["product"] == product_name] if not product_transactions: # 如果没有交易记录,重置产品数据 self.data["products"][product_name] = {"total_weight": 0, "avg_price": 0} return # 重新计算总重量和平均价格 total_weight = 0 total_value = 0 for transaction in product_transactions: if transaction["type"] == "入库": total_weight += transaction["weight"] total_value += transaction["weight"] * transaction["price"] else: # 出库 total_weight -= transaction["weight"] # 计算平均价格(只考虑入库记录) inbound_transactions = [t for t in product_transactions if t["type"] == "入库"] if inbound_transactions: total_inbound_weight = sum(t["weight"] for t in inbound_transactions) total_inbound_value = sum(t["weight"] * t["price"] for t in inbound_transactions) avg_price = total_inbound_value / total_inbound_weight if total_inbound_weight > 0 else 0 else: avg_price = 0 # 更新产品数据 self.data["products"][product_name] = { "total_weight": max(0, total_weight), # 确保重量不为负 "avg_price": avg_price } def delete_product_category(self): """删除整个产品分类""" # 确认删除 result = messagebox.askyesno("确认删除", f"确定要删除产品分类 '{self.current_product}' 吗?\n\n这将删除该产品的所有交易记录,此操作不可撤销!") if not result: return try: # 删除该产品的所有交易记录 self.data["transactions"] = [t for t in self.data["transactions"] if t["product"] != self.current_product] # 删除产品数据 if self.current_product in self.data["products"]: del self.data["products"][self.current_product] # 保存数据 self.save_data() messagebox.showinfo("成功", f"产品分类 '{self.current_product}' 已删除!") # 返回主页 self.show_main_page() except Exception as e: messagebox.showerror("错误", f"删除失败:{str(e)}") def main(): root = tk.Tk() app = InventoryApp(root) root.mainloop() if __name__ == "__main__": main()