import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import pandas as pd
import numpy as np
from tkinter.font import Font
from dataclasses import dataclass
from typing import List, Dict, Tuple
import json
import os
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
@dataclass
class Product:
"""商品数据类"""
product_code: str
product_name: str
category: str
year: int
season: str
subcategory: str
tag_price: float
mnemonic: str
color: str
@dataclass
class StoreData:
"""门店数据类"""
store_code: str
store_name: str
product_code: str
color: str
inventory: Dict[str, int] # 尺码:库存
sales_2week: Dict[str, int] # 尺码:2周销量
reorder: Dict[str, int] # 尺码:补货量
warehouse_inventory: Dict[str, int] # 区域仓库存
class FixedReplenishmentApp:
def __init__(self, root):
self.root = root
self.root.title("智能补货管理系统 - 优化版")
self.root.geometry("1920x1080")
# 设置窗口最大化
self.root.state('zoomed')
# 颜色配置 - 增强对比度
self.colors = {
'bg': '#f8f9fa',
'button_blue': '#2196F3',
'button_green': '#4CAF50',
'button_red': '#F44336',
'button_yellow': '#FFC107',
'button_orange': '#FF9800',
'header': '#ffffff',
'frame': '#ffffff',
'inventory_header': '#e3f2fd', # 库存表头 - 浅蓝
'sales_header': '#fff3e0', # 2周销表头 - 浅橙
'reorder_header': '#e8f5e9', # 补货表头 - 浅绿
'warehouse_header': '#f3e5f5', # 区域仓表头 - 浅紫
'inventory_bg': '#f0f7ff', # 库存背景色
'sales_bg': '#fffaf0', # 2周销背景色
'reorder_bg': '#f1f8e9', # 补货背景色
'warehouse_bg': '#faf4fc', # 区域仓背景色
'border': '#e0e0e0',
'text': '#333333',
'separator': '#cccccc', # 分隔线颜色
}
# 尺码定义
self.sizes = ['S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL']
# 初始化数据
self.products = []
self.store_data = []
self.filtered_products = []
self.current_product = None
self.data_modified = False
self.current_file_path = None
# 创建界面
self.setup_ui()
def setup_ui(self):
"""设置主界面"""
# 设置背景色
self.root.configure(bg=self.colors['bg'])
# 创建顶部标题栏
title_frame = tk.Frame(self.root, bg='#2196F3', height=80)
title_frame.pack(fill=tk.X, padx=0, pady=0)
title_frame.pack_propagate(False)
# 左侧标题
title_label = tk.Label(
title_frame,
text="智能补货管理系统 - 优化版",
font=("微软雅黑", 20, "bold"),
bg='#2196F3',
fg="white"
)
title_label.pack(side=tk.LEFT, padx=20, pady=20)
# 右侧状态显示
status_frame = tk.Frame(title_frame, bg='#2196F3')
status_frame.pack(side=tk.RIGHT, padx=20, pady=20)
self.selected_product_label = tk.Label(
status_frame,
text="当前选择: 未选择商品",
font=("微软雅黑", 12, "bold"),
bg='#2196F3',
fg="white"
)
self.selected_product_label.pack()
# 主内容区域
main_container = tk.Frame(self.root, bg=self.colors['bg'])
main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧面板
self.create_left_panel(main_container)
# 右侧主表格区域
self.create_main_table_area(main_container)
# 底部按钮面板
self.create_bottom_buttons()
def create_left_panel(self, parent):
"""创建左侧功能面板"""
left_frame = tk.Frame(parent, bg=self.colors['frame'], relief=tk.RAISED, borderwidth=1)
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
# 数据源管理区域
data_source_frame = tk.LabelFrame(
left_frame,
text="数据源管理",
font=("微软雅黑", 12, "bold"),
bg=self.colors['frame'],
fg="#2196F3",
relief=tk.RAISED,
borderwidth=2
)
data_source_frame.pack(fill=tk.X, padx=10, pady=(10, 5))
# 统一导入按钮
import_btn = tk.Button(
data_source_frame,
text="导入统一模板",
bg=self.colors['button_green'],
fg="white",
font=("微软雅黑", 11, "bold"),
width=20,
height=2,
command=self.import_unified_template,
relief=tk.RAISED,
bd=2
)
import_btn.pack(pady=10, padx=20, fill=tk.X)
# 导出模板按钮
export_btn = tk.Button(
data_source_frame,
text="导出统一模板",
bg=self.colors['button_blue'],
fg="white",
font=("微软雅黑", 10),
width=20,
height=1,
command=self.export_unified_template,
relief=tk.RAISED,
bd=2
)
export_btn.pack(pady=5, padx=20, fill=tk.X)
# 数据源路径显示
path_frame = tk.Frame(data_source_frame, bg=self.colors['frame'])
path_frame.pack(fill=tk.X, padx=10, pady=10)
tk.Label(
path_frame,
text="当前文件:",
bg=self.colors['frame'],
font=("微软雅黑", 9)
).pack(side=tk.LEFT)
self.data_path_label = tk.Label(
path_frame,
text="未选择文件",
bg="#f5f5f5",
fg="#666666",
relief=tk.SUNKEN,
width=20,
anchor=tk.W,
font=("微软雅黑", 9)
)
self.data_path_label.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
# 筛选条件区域
filter_frame = tk.LabelFrame(
left_frame,
text="筛选条件",
font=("微软雅黑", 12, "bold"),
bg=self.colors['frame'],
fg="#2196F3",
relief=tk.RAISED,
borderwidth=2
)
filter_frame.pack(fill=tk.X, padx=10, pady=5)
# 筛选条件输入框
filters = [
("大类:", "category"),
("商品年份:", "year"),
("季节:", "season"),
("小类:", "subcategory"),
("商品代码:", "product_code"),
("颜色:", "color"),
]
self.filter_vars = {}
for label, key in filters:
filter_row = tk.Frame(filter_frame, bg=self.colors['frame'])
filter_row.pack(fill=tk.X, padx=10, pady=3)
tk.Label(
filter_row,
text=label,
width=10,
anchor=tk.W,
bg=self.colors['frame'],
font=("微软雅黑", 9)
).pack(side=tk.LEFT)
var = tk.StringVar()
combo = ttk.Combobox(
filter_row,
textvariable=var,
width=15,
font=("微软雅黑", 9)
)
combo.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
self.filter_vars[key] = var
# 搜索框
search_frame = tk.Frame(filter_frame, bg=self.colors['frame'])
search_frame.pack(fill=tk.X, padx=10, pady=(10, 5))
tk.Label(
search_frame,
text="搜索:",
bg=self.colors['frame'],
font=("微软雅黑", 9)
).pack(side=tk.LEFT)
self.search_var = tk.StringVar()
search_entry = tk.Entry(
search_frame,
textvariable=self.search_var,
width=15,
font=("微软雅黑", 9),
relief=tk.SUNKEN,
bd=1
)
search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
search_entry.bind('<KeyRelease>', self.on_search)
# 筛选按钮
filter_btn_frame = tk.Frame(filter_frame, bg=self.colors['frame'])
filter_btn_frame.pack(fill=tk.X, padx=10, pady=(5, 10))
apply_btn = tk.Button(
filter_btn_frame,
text="应用筛选",
bg=self.colors['button_blue'],
fg="white",
font=("微软雅黑", 9),
width=10,
command=self.apply_filters,
relief=tk.RAISED,
bd=2
)
apply_btn.pack(side=tk.LEFT, padx=2)
clear_btn = tk.Button(
filter_btn_frame,
text="清除筛选",
bg=self.colors['button_red'],
fg="white",
font=("微软雅黑", 9),
width=10,
command=self.clear_filters,
relief=tk.RAISED,
bd=2
)
clear_btn.pack(side=tk.LEFT, padx=2)
# 商品列表区
list_frame = tk.LabelFrame(
left_frame,
text="商品列表",
font=("微软雅黑", 12, "bold"),
bg=self.colors['frame'],
fg="#2196F3",
relief=tk.RAISED,
borderwidth=2
)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 商品列表树
self.product_tree = ttk.Treeview(
list_frame,
columns=("name", "color", "store_count"),
show="tree headings",
height=20,
selectmode='browse'
)
# 设置样式
style = ttk.Style()
style.theme_use('clam')
# 配置列
self.product_tree.heading("#0", text="商品代码", anchor=tk.W)
self.product_tree.column("#0", width=100, minwidth=100)
self.product_tree.heading("name", text="商品名称", anchor=tk.W)
self.product_tree.column("name", width=120, minwidth=120)
self.product_tree.heading("color", text="颜色", anchor=tk.CENTER)
self.product_tree.column("color", width=60, minwidth=60, anchor=tk.CENTER)
self.product_tree.heading("store_count", text="门店数", anchor=tk.CENTER)
self.product_tree.column("store_count", width=60, minwidth=60, anchor=tk.CENTER)
# 添加滚动条
scrollbar = ttk.Scrollbar(
list_frame,
orient="vertical",
command=self.product_tree.yview
)
self.product_tree.configure(yscrollcommand=scrollbar.set)
self.product_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 绑定选择事件
self.product_tree.bind('<<TreeviewSelect>>', self.on_product_select)
# 商品数量显示
count_frame = tk.Frame(list_frame, bg=self.colors['frame'])
count_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=5)
self.product_count_label = tk.Label(
count_frame,
text="商品数量: 0",
bg=self.colors['frame'],
font=("微软雅黑", 9),
fg="#666666"
)
self.product_count_label.pack(side=tk.LEFT)
def create_main_table_area(self, parent):
"""创建主表格区域"""
right_frame = tk.Frame(parent, bg=self.colors['bg'])
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 创建表格容器
table_container = tk.Frame(right_frame, bg=self.colors['frame'], relief=tk.SUNKEN, bd=1)
table_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建表格框架
self.create_scrollable_table(table_container)
def create_scrollable_table(self, parent):
"""创建可滚动的表格"""
# 创建主框架
main_frame = tk.Frame(parent, bg=self.colors['frame'])
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建Treeview
columns = self.generate_column_names()
self.main_tree = ttk.Treeview(
main_frame,
columns=columns,
show="headings",
height=20
)
# 配置列
self.configure_table_columns()
# 创建滚动条
v_scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=self.main_tree.yview)
h_scrollbar = ttk.Scrollbar(main_frame, orient="horizontal", command=self.main_tree.xview)
self.main_tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
# 布局
self.main_tree.grid(row=0, column=0, sticky="nsew")
v_scrollbar.grid(row=0, column=1, sticky="ns")
h_scrollbar.grid(row=1, column=0, sticky="ew")
# 配置网格权重
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
# 绑定编辑事件
self.main_tree.bind('<Double-1>', self.on_cell_edit)
def generate_column_names(self):
"""生成列名 - 添加分隔列"""
columns = []
# 固定列
fixed_columns = [
"门店代码", "门店名称", "商品代码", "商品名称", "颜色",
"大类", "年份", "季节", "小类", "吊牌价"
]
columns.extend(fixed_columns)
# 添加分隔列
columns.append("分隔1")
# 库存模块
for size in self.sizes + ["合计"]:
columns.append(f"库存_{size}")
# 添加分隔列
columns.append("分隔2")
# 2周销模块
for size in self.sizes + ["合计"]:
columns.append(f"2周销_{size}")
# 添加分隔列
columns.append("分隔3")
# 补货模块
for size in self.sizes + ["合计"]:
columns.append(f"补货_{size}")
# 添加分隔列
columns.append("分隔4")
# 区域仓库存模块
for size in self.sizes + ["合计"]:
columns.append(f"区域仓_{size}")
return columns
def configure_table_columns(self):
"""配置表格列 - 优化界面清晰度"""
# 固定列宽度配置
fixed_widths = {
"门店代码": 80,
"门店名称": 100,
"商品代码": 100,
"商品名称": 150,
"颜色": 60,
"大类": 80,
"年份": 60,
"季节": 60,
"小类": 80,
"吊牌价": 80
}
# 模块列宽度配置
module_widths = {
"S": 50, "M": 50, "L": 50, "XL": 50,
"2XL": 60, "3XL": 60, "4XL": 60, "5XL": 60, "合计": 70
}
# 分隔列宽度
separator_width = 5
# 创建样式
style = ttk.Style()
style.theme_use('clam')
# 配置列标题和宽度
for col in self.main_tree['columns']:
# 设置列标题文本
if col.startswith("分隔"):
display_text = ""
width = separator_width
anchor = tk.CENTER
elif col.startswith(("库存_", "2周销_", "补货_", "区域仓_")):
# 模块列,显示模块名称和尺码
parts = col.split("_")
module_name = parts[0]
size = parts[1]
display_text = f"{module_name}\n{size}"
width = module_widths.get(size, 60)
anchor = tk.CENTER
else:
# 固定列
display_text = col
width = fixed_widths.get(col, 80)
anchor = tk.CENTER
# 设置列标题和属性
self.main_tree.heading(col, text=display_text)
self.main_tree.column(col, width=width, minwidth=width, anchor=anchor)
# 配置分隔列样式
style.configure("Separator.Treeview", background=self.colors['separator'])
def create_bottom_buttons(self):
"""创建底部操作按钮"""
button_frame = tk.Frame(self.root, bg=self.colors['bg'])
button_frame.pack(fill=tk.X, padx=10, pady=(5, 10))
button_configs = [
("导入模板", self.import_unified_template, self.colors['button_green']),
("导出模板", self.export_unified_template, self.colors['button_blue']),
("保存修改", self.save_changes, self.colors['button_green']),
("导出结果", self.export_data, self.colors['button_yellow']),
("计算补货", self.calculate_reorder, self.colors['button_orange']),
("清空数据", self.clear_data, self.colors['button_red']),
]
for text, command, color in button_configs:
btn = tk.Button(
button_frame,
text=text,
bg=color,
fg="white",
font=("微软雅黑", 10),
width=12,
height=2,
command=command,
relief=tk.RAISED,
bd=2
)
btn.pack(side=tk.LEFT, padx=5, pady=5)
# 状态栏
status_frame = tk.Frame(self.root, bg='#E0E0E0', height=25)
status_frame.pack(fill=tk.X, side=tk.BOTTOM)
status_frame.pack_propagate(False)
self.status_label = tk.Label(
status_frame,
text="就绪",
bg='#E0E0E0',
font=("微软雅黑", 9),
anchor=tk.W
)
self.status_label.pack(side=tk.LEFT, padx=10)
# 修改状态显示
self.modified_label = tk.Label(
status_frame,
text="",
bg='#E0E0E0',
font=("微软雅黑", 9),
fg="red"
)
self.modified_label.pack(side=tk.RIGHT, padx=10)
def update_status(self, message):
"""更新状态栏"""
self.status_label.config(text=f" {message}")
self.root.update()
def mark_data_modified(self):
"""标记数据已修改"""
self.data_modified = True
self.modified_label.config(text="* 数据已修改,请保存")
def import_unified_template(self):
"""导入统一模板"""
file_path = filedialog.askopenfilename(
title="选择统一模板文件",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("所有文件", "*.*")]
)
if not file_path:
return
try:
self.update_status("正在导入统一模板...")
# 读取Excel文件
if file_path.endswith('.csv'):
df = pd.read_csv(file_path, encoding='gbk')
else:
df = pd.read_excel(file_path)
# 检查必需列
required_columns = ['门店代码', '门店名称', '商品代码', '商品名称', '颜色']
for col in required_columns:
if col not in df.columns:
raise ValueError(f"缺少必需列: {col}")
# 清空现有数据
self.products.clear()
self.store_data.clear()
# 清空UI显示
for item in self.product_tree.get_children():
self.product_tree.delete(item)
for item in self.main_tree.get_children():
self.main_tree.delete(item)
# 处理数据
products_set = set() # 用于去重
for idx, row in df.iterrows():
try:
# 创建商品对象
product_code = str(row['商品代码']).strip()
color = str(row['颜色']).strip()
product_key = f"{product_code}_{color}"
if product_key not in products_set:
products_set.add(product_key)
product = Product(
product_code=product_code,
product_name=str(row.get('商品名称', '')).strip(),
category=str(row.get('大类', '')).strip(),
year=int(row.get('年份', 2023)) if not pd.isna(row.get('年份')) else 2023,
season=str(row.get('季节', '')).strip(),
subcategory=str(row.get('小类', '')).strip(),
tag_price=float(row.get('吊牌价', 0)) if not pd.isna(row.get('吊牌价')) else 0,
mnemonic="",
color=color
)
self.products.append(product)
# 创建门店数据对象
store_item = StoreData(
store_code=str(row['门店代码']).strip(),
store_name=str(row['门店名称']).strip(),
product_code=product_code,
color=color,
inventory=self.parse_size_data(row, '库存'),
sales_2week=self.parse_size_data(row, '2周销'),
reorder=self.parse_size_data(row, '补货'),
warehouse_inventory=self.parse_size_data(row, '区域仓')
)
self.store_data.append(store_item)
except Exception as e:
print(f"处理第{idx+1}行数据时出错: {e}")
continue
# 更新数据源路径显示
self.current_file_path = file_path
self.data_path_label.config(text=os.path.basename(file_path))
# 更新商品列表
self.update_product_list()
# 显示第一个商品的数据
if self.products:
first_product = self.products[0]
self.current_product = first_product
self.display_product_data(first_product.product_code, first_product.color)
self.data_modified = False
self.modified_label.config(text="")
self.update_status(f"成功导入 {len(df)} 行数据")
messagebox.showinfo("成功", f"导入完成!\n总行数: {len(df)}\n商品数量: {len(self.products)}\n门店记录: {len(self.store_data)}")
except Exception as e:
self.update_status("导入失败")
messagebox.showerror("错误", f"导入失败: {str(e)}\n\n建议先导出模板,然后按照模板格式填写数据。")
def parse_size_data(self, row, module_prefix):
"""解析尺码数据"""
data = {}
for size in self.sizes:
col_name = f"{module_prefix}_{size}"
if col_name in row:
value = row[col_name]
if pd.isna(value):
data[size] = 0
else:
try:
data[size] = int(value)
except:
data[size] = 0
else:
data[size] = 0
return data
def export_unified_template(self):
"""导出统一模板"""
file_path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")],
initialfile="统一补货模板.xlsx"
)
if not file_path:
return
try:
# 创建列定义
columns = [
'门店代码', '门店名称', '商品代码', '商品名称', '颜色',
'大类', '年份', '季节', '小类', '吊牌价'
]
# 添加各个模块的尺码列
modules = ['库存', '2周销', '补货', '区域仓']
for module in modules:
for size in self.sizes + ["合计"]:
columns.append(f"{module}_{size}")
# 创建示例数据
data = []
# 示例门店
stores = [
('S001', '北京朝阳店'),
('S002', '上海浦东店'),
('S003', '广州天河店'),
]
# 示例商品
products = [
('P001', '男士纯棉T恤', '上衣', 2023, '夏', 'T恤', 199.0, '白色'),
('P002', '运动外套', '外套', 2023, '秋', '夹克', 399.0, '黑色'),
]
# 生成示例数据
for store_code, store_name in stores:
for product_code, product_name, category, year, season, subcategory, tag_price, color in products:
row = [
store_code, store_name, product_code, product_name, color,
category, year, season, subcategory, tag_price
]
# 为每个模块添加尺码数据
for module in modules:
total = 0
for size in self.sizes:
# 生成示例数据
if module == '库存':
value = np.random.randint(5, 30)
elif module == '2周销':
value = np.random.randint(1, 10)
elif module == '补货':
value = 0
elif module == '区域仓':
value = np.random.randint(50, 100)
else:
value = 0
row.append(value)
total += value
# 添加合计
row.append(total)
data.append(row)
# 创建DataFrame
df = pd.DataFrame(data, columns=columns)
# 保存到Excel
with pd.ExcelWriter(file_path, engine='openpyxl') as writer:
df.to_excel(writer, sheet_name='补货数据', index=False)
# 设置列宽
worksheet = writer.sheets['补货数据']
for i, col in enumerate(columns, 1):
column_letter = chr(64 + i) if i <= 26 else chr(64 + i//26) + chr(64 + i%26)
# 根据列类型设置不同宽度
if '合计' in col:
worksheet.column_dimensions[column_letter].width = 8
elif any(module in col for module in modules):
worksheet.column_dimensions[column_letter].width = 6
else:
worksheet.column_dimensions[column_letter].width = 12
self.update_status("统一模板导出成功")
messagebox.showinfo("成功", f"统一模板已导出到: {file_path}")
except Exception as e:
self.update_status("导出失败")
messagebox.showerror("错误", f"导出失败: {str(e)}")
def update_product_list(self):
"""更新商品列表"""
# 清空现有列表
for item in self.product_tree.get_children():
self.product_tree.delete(item)
# 添加商品到列表
for product in self.products:
# 统计门店数量
store_count = sum(1 for s in self.store_data
if s.product_code == product.product_code and s.color == product.color)
self.product_tree.insert(
"",
"end",
text=product.product_code,
values=(product.product_name, product.color, store_count)
)
# 更新商品数量
self.product_count_label.config(text=f"商品数量: {len(self.products)}")
# 更新筛选条件选项
self.update_filter_options()
# 默认显示所有商品
self.filtered_products = self.products.copy()
def update_filter_options(self):
"""更新筛选条件选项"""
# 获取唯一值列表
categories = sorted(set(p.category for p in self.products if p.category))
years = sorted(set(str(p.year) for p in self.products if p.year))
seasons = sorted(set(p.season for p in self.products if p.season))
subcategories = sorted(set(p.subcategory for p in self.products if p.subcategory))
colors = sorted(set(p.color for p in self.products if p.color))
# 更新下拉框选项
for key, widget in self.filter_vars.items():
if hasattr(widget, 'config'): # Combobox
if key == 'category':
widget['values'] = categories
elif key == 'season':
widget['values'] = seasons
elif key == 'subcategory':
widget['values'] = subcategories
elif key == 'color':
widget['values'] = colors
elif key == 'year':
widget['values'] = years
def on_search(self, event=None):
"""搜索商品"""
search_term = self.search_var.get().lower()
# 清空列表
for item in self.product_tree.get_children():
self.product_tree.delete(item)
# 筛选并显示商品
filtered = []
for product in self.products:
if (search_term in product.product_code.lower() or
search_term in product.product_name.lower() or
search_term in product.color.lower()):
# 统计门店数量
store_count = sum(1 for s in self.store_data
if s.product_code == product.product_code and s.color == product.color)
self.product_tree.insert(
"",
"end",
text=product.product_code,
values=(product.product_name, product.color, store_count)
)
filtered.append(product)
self.filtered_products = filtered
self.product_count_label.config(text=f"商品数量: {len(filtered)}")
def on_product_select(self, event):
"""商品选择事件"""
selection = self.product_tree.selection()
if selection:
product_code = self.product_tree.item(selection[0], "text")
values = self.product_tree.item(selection[0], "values")
color = values[1] if len(values) > 1 else ""
# 查找商品对象
self.current_product = next((p for p in self.products
if p.product_code == product_code and p.color == color), None)
if self.current_product:
# 更新已选择商品显示
self.selected_product_label.config(
text=f"当前选择: {product_code} - {self.current_product.product_name}({color})"
)
# 显示该商品的数据
self.display_product_data(product_code, color)
def display_product_data(self, product_code, color):
"""显示选定商品的数据"""
# 清空表格
for item in self.main_tree.get_children():
self.main_tree.delete(item)
if not self.store_data:
return
# 筛选该商品的数据
product_data = [item for item in self.store_data
if item.product_code == product_code and item.color == color]
if not product_data:
return
# 查找商品信息
product = next((p for p in self.products
if p.product_code == product_code and p.color == color), None)
if not product:
return
# 添加数据到表格
for store_item in product_data:
row_values = []
# 固定列数据
row_values.extend([
store_item.store_code,
store_item.store_name,
store_item.product_code,
product.product_name,
store_item.color,
product.category,
product.year,
product.season,
product.subcategory,
product.tag_price
])
# 分隔列1
row_values.append("")
# 库存数据
inventory_total = 0
for size in self.sizes:
value = store_item.inventory.get(size, 0)
row_values.append(value)
inventory_total += value
row_values.append(inventory_total)
# 分隔列2
row_values.append("")
# 2周销数据
sales_total = 0
for size in self.sizes:
value = store_item.sales_2week.get(size, 0)
row_values.append(value)
sales_total += value
row_values.append(sales_total)
# 分隔列3
row_values.append("")
# 补货数据
reorder_total = 0
for size in self.sizes:
value = store_item.reorder.get(size, 0)
row_values.append(value)
reorder_total += value
row_values.append(reorder_total)
# 分隔列4
row_values.append("")
# 区域仓库存数据
warehouse_total = 0
for size in self.sizes:
value = store_item.warehouse_inventory.get(size, 0)
row_values.append(value)
warehouse_total += value
row_values.append(warehouse_total)
# 插入行
self.main_tree.insert("", "end", values=row_values)
def on_cell_edit(self, event):
"""单元格编辑事件 - 优化实时加总功能"""
tree = self.main_tree
region = tree.identify_region(event.x, event.y)
if region == "cell":
column = tree.identify_column(event.x)
row_id = tree.identify_row(event.y)
# 获取列索引和列名
col_idx = int(column[1:]) - 1
col_name = tree.heading(column)["text"]
# 检查是否在补货区域
# 计算各个模块的起始索引(考虑分隔列)
fixed_cols_count = 10
separator_count = 1 # 第一个分隔列
module_cols_count = len(self.sizes) + 1
# 补货区域起始索引
reorder_start_idx = (fixed_cols_count + separator_count + # 固定列+分隔1
module_cols_count + # 库存模块
separator_count + # 分隔2
module_cols_count + # 2周销模块
separator_count) # 分隔3
reorder_end_idx = reorder_start_idx + len(self.sizes) # 不包括合计列
if reorder_start_idx <= col_idx < reorder_end_idx:
x, y, width, height = tree.bbox(row_id, column)
# 获取当前值
current_value = tree.item(row_id, "values")[col_idx]
# 创建编辑框
entry = tk.Entry(tree, justify=tk.CENTER, font=("微软雅黑", 9))
entry.place(x=x, y=y, width=width, height=height)
entry.insert(0, current_value)
entry.focus_set()
entry.select_range(0, tk.END)
def save_edit(event=None):
try:
new_value = int(entry.get())
if new_value < 0:
raise ValueError("补货量不能为负数")
# 获取当前行数据
row_values = list(tree.item(row_id, "values"))
# 获取尺码索引
size_idx = col_idx - reorder_start_idx
size = self.sizes[size_idx]
# 获取门店信息
store_code = row_values[0]
product_code = row_values[2]
color = row_values[4]
# 找到对应的store_item
store_item = next(
(item for item in self.store_data
if item.store_code == store_code
and item.product_code == product_code
and item.color == color),
None
)
if store_item:
# 获取区域仓库存
warehouse_col_idx = col_idx + module_cols_count + separator_count # 跳过补货合计列和分隔4
warehouse_qty = store_item.warehouse_inventory.get(size, 0)
# 验证补货量不超过区域仓库存
if new_value > warehouse_qty:
raise ValueError(f"补货量不能超过区域仓库存({warehouse_qty})")
# 更新补货量
old_value = store_item.reorder.get(size, 0)
store_item.reorder[size] = new_value
# 重新计算补货合计
reorder_total_idx = reorder_start_idx + len(self.sizes)
old_total = int(row_values[reorder_total_idx]) if row_values[reorder_total_idx] else 0
new_total = old_total - old_value + new_value
row_values[reorder_total_idx] = new_total
# 更新区域仓库存
warehouse_change = old_value - new_value
new_warehouse_qty = warehouse_qty + warehouse_change
store_item.warehouse_inventory[size] = new_warehouse_qty
# 更新区域仓库存显示
row_values[warehouse_col_idx] = new_warehouse_qty
# 重新计算区域仓库存合计
warehouse_total_idx = warehouse_col_idx + len(self.sizes) + 1
current_warehouse_total = int(row_values[warehouse_total_idx]) if row_values[warehouse_total_idx] else 0
row_values[warehouse_total_idx] = current_warehouse_total + warehouse_change
# 更新表格
row_values[col_idx] = new_value
tree.item(row_id, values=row_values)
entry.destroy()
# 标记数据已修改
self.mark_data_modified()
self.update_status("补货量已更新并实时加总")
except ValueError as e:
messagebox.showerror("输入错误", str(e))
entry.focus_set()
entry.bind("<Return>", save_edit)
entry.bind("<FocusOut>", lambda e: entry.destroy())
def apply_filters(self):
"""应用筛选条件"""
# 获取筛选条件
filters = {}
for key, var in self.filter_vars.items():
value = var.get()
if value:
filters[key] = value
# 清空列表
for item in self.product_tree.get_children():
self.product_tree.delete(item)
# 筛选商品
self.filtered_products = []
for product in self.products:
match = True
for key, value in filters.items():
product_value = getattr(product, key, "")
if key == 'year':
if str(product_value) != value:
match = False
break
elif str(product_value).lower() != str(value).lower():
match = False
break
if match:
store_count = sum(1 for s in self.store_data
if s.product_code == product.product_code and s.color == product.color)
self.product_tree.insert(
"",
"end",
text=product.product_code,
values=(product.product_name, product.color, store_count)
)
self.filtered_products.append(product)
self.product_count_label.config(text=f"商品数量: {len(self.filtered_products)}")
def clear_filters(self):
"""清除筛选条件"""
for var in self.filter_vars.values():
var.set("")
# 显示所有商品
self.update_product_list()
def save_changes(self):
"""保存修改"""
try:
if not self.current_file_path:
# 如果没有文件路径,提示用户选择保存位置
file_path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")],
initialfile=f"补货数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
)
if not file_path:
return
self.current_file_path = file_path
# 构建保存数据
columns = self.generate_column_names()
data = []
# 收集所有数据
for store_item in self.store_data:
# 查找对应的商品信息
product = next((p for p in self.products
if p.product_code == store_item.product_code and p.color == store_item.color), None)
if product:
row = []
# 固定列
row.extend([
store_item.store_code,
store_item.store_name,
store_item.product_code,
product.product_name,
store_item.color,
product.category,
product.year,
product.season,
product.subcategory,
product.tag_price
])
# 分隔列1
row.append("")
# 库存数据
inventory_total = 0
for size in self.sizes:
value = store_item.inventory.get(size, 0)
row.append(value)
inventory_total += value
row.append(inventory_total)
# 分隔列2
row.append("")
# 2周销数据
sales_total = 0
for size in self.sizes:
value = store_item.sales_2week.get(size, 0)
row.append(value)
sales_total += value
row.append(sales_total)
# 分隔列3
row.append("")
# 补货数据
reorder_total = 0
for size in self.sizes:
value = store_item.reorder.get(size, 0)
row.append(value)
reorder_total += value
row.append(reorder_total)
# 分隔列4
row.append("")
# 区域仓库存数据
warehouse_total = 0
for size in self.sizes:
value = store_item.warehouse_inventory.get(size, 0)
row.append(value)
warehouse_total += value
row.append(warehouse_total)
data.append(row)
# 创建DataFrame并保存
df = pd.DataFrame(data, columns=columns)
# 移除分隔列(保存时不需要)
separator_cols = [col for col in columns if col.startswith("分隔")]
df = df.drop(columns=separator_cols)
if self.current_file_path.endswith('.csv'):
df.to_csv(self.current_file_path, index=False, encoding='gbk')
else:
df.to_excel(self.current_file_path, index=False)
self.data_modified = False
self.modified_label.config(text="")
self.update_status("数据保存成功")
messagebox.showinfo("成功", f"数据已保存到: {self.current_file_path}")
except Exception as e:
self.update_status("保存失败")
messagebox.showerror("错误", f"保存失败: {str(e)}")
def export_data(self):
"""导出当前表格数据"""
file_path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("CSV文件", "*.csv"), ("所有文件", "*.*")],
initialfile=f"补货结果_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
)
if not file_path:
return
try:
# 收集表格数据
data = []
# 获取所有行
for item in self.main_tree.get_children():
values = self.main_tree.item(item, "values")
data.append(values)
if data:
# 创建列名
columns = self.generate_column_names()
# 创建DataFrame
df = pd.DataFrame(data, columns=columns)
# 移除分隔列(导出时不需要)
separator_cols = [col for col in columns if col.startswith("分隔")]
df = df.drop(columns=separator_cols)
# 保存文件
if file_path.endswith('.csv'):
df.to_csv(file_path, index=False, encoding='gbk')
else:
df.to_excel(file_path, index=False)
self.update_status(f"数据已导出到: {file_path}")
messagebox.showinfo("成功", f"数据已导出到: {file_path}")
else:
messagebox.showwarning("警告", "没有数据可导出")
except Exception as e:
self.update_status("导出失败")
messagebox.showerror("错误", f"导出失败: {str(e)}")
def calculate_reorder(self):
"""自动计算补货建议 - 优化算法"""
try:
# 更智能的补货逻辑
for store_item in self.store_data:
for size in self.sizes:
inventory = store_item.inventory.get(size, 0)
sales = store_item.sales_2week.get(size, 0)
warehouse_qty = store_item.warehouse_inventory.get(size, 0)
# 计算安全库存(2周销量的1.5倍)
safety_stock = max(sales * 1.5, 5) # 最小安全库存为5
# 如果库存低于安全库存,建议补货
if inventory < safety_stock and warehouse_qty > 0:
# 补货量 = 安全库存 - 当前库存,但不超过区域仓库存
suggested = min(safety_stock - inventory, warehouse_qty)
store_item.reorder[size] = int(suggested)
else:
store_item.reorder[size] = 0
# 刷新显示
if self.current_product:
self.display_product_data(self.current_product.product_code, self.current_product.color)
self.mark_data_modified()
self.update_status("智能补货计算完成")
messagebox.showinfo("成功", "智能补货计算完成,建议已生成")
except Exception as e:
self.update_status("计算失败")
messagebox.showerror("错误", f"计算失败: {str(e)}")
def clear_data(self):
"""清空数据"""
if self.data_modified:
if not messagebox.askyesno("确认", "有未保存的修改,确定要清空所有数据吗?"):
return
if messagebox.askyesno("确认", "确定要清空所有数据吗?"):
# 清空数据
self.products.clear()
self.store_data.clear()
self.filtered_products.clear()
self.current_product = None
# 重置UI
for item in self.product_tree.get_children():
self.product_tree.delete(item)
for item in self.main_tree.get_children():
self.main_tree.delete(item)
self.product_count_label.config(text="商品数量: 0")
self.selected_product_label.config(text="当前选择: 未选择商品")
self.data_path_label.config(text="未选择文件")
self.current_file_path = None
self.data_modified = False
self.modified_label.config(text="")
self.update_status("数据已清空")
def main():
"""主函数"""
root = tk.Tk()
app = FixedReplenishmentApp(root)
root.mainloop()
if __name__ == "__main__":
main()
这个代码要怎么修改如何实现:
1、在补货板块输入时 ,实时显示保存输入的量及加总。
2、另外库存板块、2周销板块、补货板块、区域仓库存能在界面上能很好的区分开来然后底下都有对应的S M XL 2XL 3XL 4XL 5XL等。每个板块要有合计汇总功能。
4、区域仓库存是用于给门店补货的总数据,区域仓库库存也是实时变动,例我给A店A款A色补S码对应区域仓的库存也会实时肉眼能看到变动,还有就是我A款A色A码前面已经补给其中1家门店 当我给另外一家店补的时候界面显示的一定是最新扣减后的库存。
3、界面右边要冻结吊牌价前面列的所有字段,也就是我水平滚动条右拉时候这几列是冻结的。
4、导入模版需帮忙再优化下
希望高手能帮忙优化下,提供给我完整代码 谢谢
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
from dataclasses import dataclass
from typing import Dict
from datetime import datetime
# -----------------------------
# 数据类定义
# -----------------------------
@dataclass
class Product:
"""商品数据类"""
product_code: str
product_name: str
category: str
year: int
season: str
subcategory: str
tag_price: float
color: str
@dataclass
class StoreData:
"""门店数据类"""
store_code: str
store_name: str
product_code: str
color: str
inventory: Dict[str, int]
sales_2week: Dict[str, int]
reorder: Dict[str, int]
warehouse_inventory: Dict[str, int]
# -----------------------------
# 主应用类
# -----------------------------
class FixedReplenishmentApp:
def __init__(self, root):
self.root = root
self.root.title("智能补货管理系统 - 优化版")
self.root.state('zoomed')
self.sizes = ['S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL']
self.products = []
self.store_data = []
self.current_product = None
self.setup_ui()
# -----------------------------
# UI布局
# -----------------------------
def setup_ui(self):
title_frame = tk.Frame(self.root, bg='#2196F3', height=60)
title_frame.pack(fill=tk.X)
tk.Label(
title_frame,
text="智能补货管理系统 - 优化版",
font=("微软雅黑", 18, "bold"),
bg='#2196F3',
fg='white'
).pack(side=tk.LEFT, padx=20)
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True)
# 左侧商品列表
left_frame = tk.Frame(main_frame, width=300)
left_frame.pack(side=tk.LEFT, fill=tk.Y)
self.product_tree = ttk.Treeview(
left_frame,
columns=("name", "color"),
show="headings"
)
self.product_tree.heading("name", text="商品名称")
self.product_tree.heading("color", text="颜色")
self.product_tree.pack(fill=tk.BOTH, expand=True)
self.product_tree.bind("<<TreeviewSelect>>", self.on_product_select)
# 右侧表格
right_frame = tk.Frame(main_frame)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
self.fixed_tree = ttk.Treeview(right_frame, show="headings", height=20)
self.scrollable_tree = ttk.Treeview(right_frame, show="headings", height=20)
v_scroll = ttk.Scrollbar(right_frame, orient="vertical", command=self.on_vertical_scroll)
v_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.fixed_tree.pack(side=tk.LEFT, fill=tk.Y)
self.scrollable_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.fixed_tree.configure(yscrollcommand=v_scroll.set)
self.scrollable_tree.configure(yscrollcommand=v_scroll.set)
# 底部按钮
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(fill=tk.X)
tk.Button(bottom_frame, text="导入模板", command=self.import_template).pack(side=tk.LEFT, padx=5)
tk.Button(bottom_frame, text="导出模板", command=self.export_template).pack(side=tk.LEFT, padx=5)
tk.Button(bottom_frame, text="保存修改", command=self.save_changes).pack(side=tk.LEFT, padx=5)
tk.Button(bottom_frame, text="计算补货", command=self.calculate_reorder).pack(side=tk.LEFT, padx=5)
def on_vertical_scroll(self, *args):
self.fixed_tree.yview(*args)
self.scrollable_tree.yview(*args)
# -----------------------------
# 模板导入
# -----------------------------
def import_template(self):
file_path = filedialog.askopenfilename(
title="选择模板",
filetypes=[("Excel文件", "*.xlsx")]
)
if not file_path:
return
df = pd.read_excel(file_path)
self.products.clear()
self.store_data.clear()
for _, row in df.iterrows():
product = Product(
product_code=row['商品代码'],
product_name=row['商品名称'],
category=row['大类'],
year=int(row['年份']),
season=row['季节'],
subcategory=row['小类'],
tag_price=float(row['吊牌价']),
color=row['颜色']
)
self.products.append(product)
store_item = StoreData(
store_code=row['门店代码'],
store_name=row['门店名称'],
product_code=row['商品代码'],
color=row['颜色'],
inventory={s: int(row.get(f'库存{s}', 0)) for s in self.sizes},
sales_2week={s: int(row.get(f'2周销{s}', 0)) for s in self.sizes},
reorder={s: int(row.get(f'补货{s}', 0)) for s in self.sizes},
warehouse_inventory={s: int(row.get(f'区域仓_{s}', 0)) for s in self.sizes}
)
self.store_data.append(store_item)
self.update_product_list()
# -----------------------------
# 商品列表更新
# -----------------------------
def update_product_list(self):
self.product_tree.delete(*self.product_tree.get_children())
for product in self.products:
self.product_tree.insert('', 'end', values=(product.product_name, product.color))
# -----------------------------
# 商品选择
# -----------------------------
def on_product_select(self, event):
selection = self.product_tree.selection()
if selection:
index = self.product_tree.index(selection[0])
self.current_product = self.products[index]
self.display_product_data()
# -----------------------------
# 显示商品数据
# -----------------------------
def display_product_data(self):
self.fixed_tree.delete(*self.fixed_tree.get_children())
self.scrollable_tree.delete(*self.scrollable_tree.get_children())
fixed_cols = ['门店代码', '门店名称', '商品代码', '颜色', '吊牌价']
scroll_cols = [
f"{m}{s}"
for m in ['库存', '2周销', '补货', '区域仓']
for s in self.sizes
]
self.fixed_tree["columns"] = fixed_cols
self.scrollable_tree["columns"] = scroll_cols
for col in fixed_cols:
self.fixed_tree.heading(col, text=col)
self.fixed_tree.column(col, width=120)
for col in scroll_cols:
self.scrollable_tree.heading(col, text=col)
self.scrollable_tree.column(col, width=60)
for store in self.store_data:
if store.product_code == self.current_product.product_code and store.color == self.current_product.color:
fixed_values = [
store.store_code,
store.store_name,
store.product_code,
store.color,
self.current_product.tag_price
]
scroll_values = []
for d in [store.inventory, store.sales_2week, store.reorder, store.warehouse_inventory]:
for s in self.sizes:
scroll_values.append(d.get(s, 0))
self.fixed_tree.insert('', 'end', values=fixed_values)
self.scrollable_tree.insert('', 'end', values=scroll_values)
# -----------------------------
# 智能补货计算
# -----------------------------
def calculate_reorder(self):
for store in self.store_data:
for size in self.sizes:
inventory = store.inventory.get(size, 0)
sales = store.sales_2week.get(size, 0)
warehouse = store.warehouse_inventory.get(size, 0)
safety_stock = max(int(sales * 1.5), 5)
if inventory < safety_stock and warehouse > 0:
store.reorder[size] = min(safety_stock - inventory, warehouse)
else:
store.reorder[size] = 0
if self.current_product:
self.display_product_data()
messagebox.showinfo("完成", "补货计算完成")
# -----------------------------
# 保存
# -----------------------------
def save_changes(self):
file_path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx")]
)
if not file_path:
return
columns = ['门店代码', '门店名称', '商品代码', '颜色']
for m in ['库存', '2周销', '补货', '区域仓']:
for s in self.sizes:
columns.append(f'{m}_{s}')
data = []
for store in self.store_data:
row = [store.store_code, store.store_name, store.product_code, store.color]
for d in [store.inventory, store.sales_2week, store.reorder, store.warehouse_inventory]:
for s in self.sizes:
row.append(d.get(s, 0))
data.append(row)
pd.DataFrame(data, columns=columns).to_excel(file_path, index=False)
messagebox.showinfo("成功", "数据已保存")
# -----------------------------
# 主入口
# -----------------------------
def main():
root = tk.Tk()
app = FixedReplenishmentApp(root)
root.mainloop()
if __name__ == "__main__":
main()Python代码运行助手可以让你在线输入Python代码,只需要在网页输入代码,然后点击Run按钮,代码被发送到远程执行后,在网页显示代码执行结果: 试试效果 需要支持HTML5的浏览器: IE >= 9 Edge Firefox Chrome Safari # 测试代码: ---- print('Hello, world')
不清楚这样的问题该怎么起标题…… 这是一个简单的格式化长度的方法,以米为基础单位,这是我自己写的,请问有更简洁的写法吗?谢谢
本文向大家介绍Python在线运行代码助手,包括了Python在线运行代码助手的使用技巧和注意事项,需要的朋友参考一下 Python代码运行助手可以让你在线输入Python代码,然后通过本机运行的一个Python脚本来执行代码。原理如下: 在网页输入代码: 点击Run按钮,代码被发送到本机正在运行的Python代码运行助手; Python代码运行助手将代码保存为临时文件,然后调用Python解释器
本文向大家介绍Python中协程用法代码详解,包括了Python中协程用法代码详解的使用技巧和注意事项,需要的朋友参考一下 本文研究的主要是python中协程的相关问题,具体介绍如下。 Num01–>协程的定义 协程,又称微线程,纤程。英文名Coroutine。 首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。 为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,
Donald Knuth "过早的优化是一切罪恶的根源" 本章处理用策略让Python代码跑得更快。 先决条件 line_profiler gprof2dot 来自dot实用程序 2.4.1 优化工作流 让它工作起来:用简单清晰的方式来写代码。 让它可靠的工作:写自动的测试案例,以便真正确保你的算法是正确的,并且如果你破坏它,测试会捕捉到。 通过剖析简单的使用案例找到瓶颈,并且加速这些瓶颈,寻找更
问题内容: 我正在研究django中的模型系统如何工作,我注意到一些我不理解的东西。 我知道您创建了一个空文件来指定当前目录是一个包。并且您可以在其中设置一些变量,以便import *正常工作。 但是django添加了一堆from … import …语句,并在中定义了一堆类。为什么?这不仅会使事情看起来凌乱吗?是否有需要此代码的原因? 问题答案: 当导入包含它的包(目录)时,所有导入都可用。 例
有没有代码写的漂亮的大佬,看看这个代码怎么优化,一直写前端的,突然被叫去搞java,发现很多技术都不太相同,例如动态的key去调用之类,导致写出这样的恶心代码,自己都看不下去了 明明js可以写的这么短小优雅,java有没有办法做到这样子的呢
我想写一个模拟 DNF 装备增幅的程序,通过多次样本执行得到平均每件增幅 10 装备需要增幅多少次。装备 +4 之前不会失败,+4 之后会失败且失败后还会掉级,具体如下图所示: 公会秘药和普雷宠物会额外增加每次增幅的成功率 1% 和 4%,所以一共分了三种情况。 我最开始用 js 写了一版: 后来想到我刚学了 rust,不如练练手,而且 rust 很快,于是又写了一版: 然而实际上 rust 代码