commit 10c80df7e76faeb4da81c7e9e629a5d8aaca75a3 Author: spdis Date: Fri Jul 25 21:18:30 2025 +0800 添加 Simple_Enterprise_Warehouse_Management.py diff --git a/Simple_Enterprise_Warehouse_Management.py b/Simple_Enterprise_Warehouse_Management.py new file mode 100644 index 0000000..29e9528 --- /dev/null +++ b/Simple_Enterprise_Warehouse_Management.py @@ -0,0 +1,999 @@ +#本产品含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() \ No newline at end of file